2024-09-19 12:58:38 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2023 , Andreas Kling < andreas @ ladybird . org >
2024-09-19 12:58:38 +01:00
* Copyright ( c ) 2021 , the SerenityOS developers .
* Copyright ( c ) 2021 - 2024 , Sam Atkins < sam @ ladybird . org >
* Copyright ( c ) 2024 , Matthew Olsson < mattco @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include "Interpolation.h"
# include <LibWeb/CSS/PropertyID.h>
2025-02-18 09:19:56 +01:00
# include <LibWeb/CSS/StyleComputer.h>
2024-09-19 12:58:38 +01:00
# include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
# include <LibWeb/CSS/StyleValues/CSSColorValue.h>
# include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
2025-01-08 16:14:17 +00:00
# include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
2024-09-19 12:58:38 +01:00
# include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
# include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
# include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
# include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
# include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
# include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
# include <LibWeb/CSS/StyleValues/RectStyleValue.h>
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
# include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
# include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
# include <LibWeb/CSS/Transformation.h>
# include <LibWeb/DOM/Element.h>
# include <LibWeb/Layout/Node.h>
# include <LibWeb/Painting/PaintableBox.h>
namespace Web : : CSS {
template < typename T >
static T interpolate_raw ( T from , T to , float delta )
{
if constexpr ( AK : : Detail : : IsSame < T , double > ) {
return from + ( to - from ) * static_cast < double > ( delta ) ;
} else {
return static_cast < AK : : Detail : : RemoveCVReference < T > > ( from + ( to - from ) * delta ) ;
}
}
2024-11-07 17:44:22 +01:00
static NonnullRefPtr < CSSStyleValue const > with_keyword_values_resolved ( DOM : : Element & element , PropertyID property_id , CSSStyleValue const & value )
2024-09-19 12:58:38 +01:00
{
2024-11-07 17:44:22 +01:00
if ( ! value . is_keyword ( ) )
return value ;
switch ( value . as_keyword ( ) . keyword ( ) ) {
case CSS : : Keyword : : Initial :
case CSS : : Keyword : : Unset :
2024-12-05 10:58:21 +00:00
return property_initial_value ( property_id ) ;
2024-11-07 17:44:22 +01:00
case CSS : : Keyword : : Inherit :
2024-12-05 11:08:58 +00:00
return CSS : : StyleComputer : : get_inherit_value ( property_id , & element ) ;
2024-11-07 17:44:22 +01:00
default :
break ;
}
return value ;
}
ValueComparingRefPtr < CSSStyleValue const > interpolate_property ( DOM : : Element & element , PropertyID property_id , CSSStyleValue const & a_from , CSSStyleValue const & a_to , float delta )
{
auto from = with_keyword_values_resolved ( element , property_id , a_from ) ;
auto to = with_keyword_values_resolved ( element , property_id , a_to ) ;
2025-01-08 16:14:17 +00:00
CalculationContext calculation_context {
. percentages_resolve_as = property_resolves_percentages_relative_to ( property_id ) ,
} ;
2024-09-19 12:58:38 +01:00
auto animation_type = animation_type_from_longhand_property ( property_id ) ;
switch ( animation_type ) {
case AnimationType : : ByComputedValue :
2025-01-08 16:14:17 +00:00
return interpolate_value ( element , calculation_context , from , to , delta ) ;
2024-09-19 12:58:38 +01:00
case AnimationType : : None :
return to ;
case AnimationType : : Custom : {
if ( property_id = = PropertyID : : Transform ) {
if ( auto interpolated_transform = interpolate_transform ( element , from , to , delta ) )
return * interpolated_transform ;
// https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
// In some cases, an animation might cause a transformation matrix to be singular or non-invertible.
// For example, an animation in which scale moves from 1 to -1. At the time when the matrix is in
// such a state, the transformed element is not rendered.
return { } ;
}
if ( property_id = = PropertyID : : BoxShadow )
2025-01-08 16:14:17 +00:00
return interpolate_box_shadow ( element , calculation_context , from , to , delta ) ;
2024-09-19 12:58:38 +01:00
// FIXME: Handle all custom animatable properties
[[fallthrough]] ;
}
// FIXME: Handle repeatable-list animatable properties
case AnimationType : : RepeatableList :
case AnimationType : : Discrete :
default :
return delta > = 0.5f ? to : from ;
}
}
2024-09-19 14:13:20 +01:00
// https://drafts.csswg.org/css-transitions/#transitionable
bool property_values_are_transitionable ( PropertyID property_id , CSSStyleValue const & old_value , CSSStyleValue const & new_value )
{
// When comparing the before-change style and after-change style for a given property,
// the property values are transitionable if they have an animation type that is neither not animatable nor discrete.
auto animation_type = animation_type_from_longhand_property ( property_id ) ;
if ( animation_type = = AnimationType : : None | | animation_type = = AnimationType : : Discrete )
return false ;
// FIXME: Even when a property is transitionable, the two values may not be. The spec uses the example of inset/non-inset shadows.
( void ) old_value ;
( void ) new_value ;
return true ;
}
2024-09-19 12:58:38 +01:00
// A null return value means the interpolated matrix was not invertible or otherwise invalid
RefPtr < CSSStyleValue const > interpolate_transform ( DOM : : Element & element , CSSStyleValue const & from , CSSStyleValue const & to , float delta )
{
// Note that the spec uses column-major notation, so all the matrix indexing is reversed.
2025-01-13 15:13:12 +00:00
static constexpr auto make_transformation = [ ] ( TransformationStyleValue const & transformation ) - > Optional < Transformation > {
Vector < TransformValue > values ;
2024-09-19 12:58:38 +01:00
for ( auto const & value : transformation . values ( ) ) {
switch ( value - > type ( ) ) {
case CSSStyleValue : : Type : : Angle :
values . append ( AngleOrCalculated { value - > as_angle ( ) . angle ( ) } ) ;
break ;
2025-01-13 15:13:12 +00:00
case CSSStyleValue : : Type : : Calculated : {
auto & calculated = value - > as_calculated ( ) ;
if ( calculated . resolves_to_angle ( ) ) {
values . append ( AngleOrCalculated { calculated } ) ;
} else if ( calculated . resolves_to_length_percentage ( ) ) {
values . append ( LengthPercentage { calculated } ) ;
} else if ( calculated . resolves_to_number ( ) ) {
values . append ( NumberPercentage { calculated } ) ;
} else {
dbgln ( " Calculation `{}` inside {} transform-function is not a recognized type " , calculated . to_string ( CSSStyleValue : : SerializationMode : : Normal ) , to_string ( transformation . transform_function ( ) ) ) ;
return { } ;
}
2024-09-19 12:58:38 +01:00
break ;
2025-01-13 15:13:12 +00:00
}
2024-09-19 12:58:38 +01:00
case CSSStyleValue : : Type : : Length :
values . append ( LengthPercentage { value - > as_length ( ) . length ( ) } ) ;
break ;
case CSSStyleValue : : Type : : Percentage :
values . append ( LengthPercentage { value - > as_percentage ( ) . percentage ( ) } ) ;
break ;
case CSSStyleValue : : Type : : Number :
values . append ( NumberPercentage { Number ( Number : : Type : : Number , value - > as_number ( ) . number ( ) ) } ) ;
break ;
default :
return { } ;
}
}
return Transformation { transformation . transform_function ( ) , move ( values ) } ;
} ;
static constexpr auto transformation_style_value_to_matrix = [ ] ( DOM : : Element & element , TransformationStyleValue const & value ) - > Optional < FloatMatrix4x4 > {
auto transformation = make_transformation ( value ) ;
if ( ! transformation . has_value ( ) )
return { } ;
Optional < Painting : : PaintableBox const & > paintable_box ;
if ( auto layout_node = element . layout_node ( ) ) {
2024-10-16 15:19:32 +02:00
if ( auto paintable = layout_node - > first_paintable ( ) ; paintable & & is < Painting : : PaintableBox > ( paintable ) )
2024-09-19 12:58:38 +01:00
paintable_box = * static_cast < Painting : : PaintableBox * > ( paintable ) ;
}
if ( auto matrix = transformation - > to_matrix ( paintable_box ) ; ! matrix . is_error ( ) )
return matrix . value ( ) ;
return { } ;
} ;
static constexpr auto style_value_to_matrix = [ ] ( DOM : : Element & element , CSSStyleValue const & value ) - > FloatMatrix4x4 {
if ( value . is_transformation ( ) )
return transformation_style_value_to_matrix ( element , value . as_transformation ( ) ) . value_or ( FloatMatrix4x4 : : identity ( ) ) ;
// This encompasses both the allowed value "none" and any invalid values
if ( ! value . is_value_list ( ) )
return FloatMatrix4x4 : : identity ( ) ;
auto matrix = FloatMatrix4x4 : : identity ( ) ;
for ( auto const & value_element : value . as_value_list ( ) . values ( ) ) {
if ( value_element - > is_transformation ( ) ) {
if ( auto value_matrix = transformation_style_value_to_matrix ( element , value_element - > as_transformation ( ) ) ; value_matrix . has_value ( ) )
matrix = matrix * value_matrix . value ( ) ;
}
}
return matrix ;
} ;
struct DecomposedValues {
FloatVector3 translation ;
FloatVector3 scale ;
FloatVector3 skew ;
FloatVector4 rotation ;
FloatVector4 perspective ;
} ;
// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
static constexpr auto decompose = [ ] ( FloatMatrix4x4 matrix ) - > Optional < DecomposedValues > {
// https://drafts.csswg.org/css-transforms-1/#supporting-functions
static constexpr auto combine = [ ] ( auto a , auto b , float ascl , float bscl ) {
return FloatVector3 {
ascl * a [ 0 ] + bscl * b [ 0 ] ,
ascl * a [ 1 ] + bscl * b [ 1 ] ,
ascl * a [ 2 ] + bscl * b [ 2 ] ,
} ;
} ;
// Normalize the matrix.
if ( matrix ( 3 , 3 ) = = 0.f )
return { } ;
for ( int i = 0 ; i < 4 ; i + + )
for ( int j = 0 ; j < 4 ; j + + )
matrix ( i , j ) / = matrix ( 3 , 3 ) ;
// perspectiveMatrix is used to solve for perspective, but it also provides
// an easy way to test for singularity of the upper 3x3 component.
auto perspective_matrix = matrix ;
for ( int i = 0 ; i < 3 ; i + + )
perspective_matrix ( 3 , i ) = 0.f ;
perspective_matrix ( 3 , 3 ) = 1.f ;
if ( ! perspective_matrix . is_invertible ( ) )
return { } ;
DecomposedValues values ;
// First, isolate perspective.
if ( matrix ( 3 , 0 ) ! = 0.f | | matrix ( 3 , 1 ) ! = 0.f | | matrix ( 3 , 2 ) ! = 0.f ) {
// rightHandSide is the right hand side of the equation.
// Note: It is the bottom side in a row-major matrix
FloatVector4 bottom_side = {
matrix ( 3 , 0 ) ,
matrix ( 3 , 1 ) ,
matrix ( 3 , 2 ) ,
matrix ( 3 , 3 ) ,
} ;
// Solve the equation by inverting perspectiveMatrix and multiplying
// rightHandSide by the inverse.
auto inverse_perspective_matrix = perspective_matrix . inverse ( ) ;
auto transposed_inverse_perspective_matrix = inverse_perspective_matrix . transpose ( ) ;
values . perspective = transposed_inverse_perspective_matrix * bottom_side ;
} else {
// No perspective.
values . perspective = { 0.0 , 0.0 , 0.0 , 1.0 } ;
}
// Next take care of translation
for ( int i = 0 ; i < 3 ; i + + )
values . translation [ i ] = matrix ( i , 3 ) ;
// Now get scale and shear. 'row' is a 3 element array of 3 component vectors
FloatVector3 row [ 3 ] ;
for ( int i = 0 ; i < 3 ; i + + )
row [ i ] = { matrix ( 0 , i ) , matrix ( 1 , i ) , matrix ( 2 , i ) } ;
// Compute X scale factor and normalize first row.
values . scale [ 0 ] = row [ 0 ] . length ( ) ;
row [ 0 ] . normalize ( ) ;
// Compute XY shear factor and make 2nd row orthogonal to 1st.
values . skew [ 0 ] = row [ 0 ] . dot ( row [ 1 ] ) ;
row [ 1 ] = combine ( row [ 1 ] , row [ 0 ] , 1.f , - values . skew [ 0 ] ) ;
// Now, compute Y scale and normalize 2nd row.
values . scale [ 1 ] = row [ 1 ] . length ( ) ;
row [ 1 ] . normalize ( ) ;
values . skew [ 0 ] / = values . scale [ 1 ] ;
// Compute XZ and YZ shears, orthogonalize 3rd row
values . skew [ 1 ] = row [ 0 ] . dot ( row [ 2 ] ) ;
row [ 2 ] = combine ( row [ 2 ] , row [ 0 ] , 1.f , - values . skew [ 1 ] ) ;
values . skew [ 2 ] = row [ 1 ] . dot ( row [ 2 ] ) ;
row [ 2 ] = combine ( row [ 2 ] , row [ 1 ] , 1.f , - values . skew [ 2 ] ) ;
// Next, get Z scale and normalize 3rd row.
values . scale [ 2 ] = row [ 2 ] . length ( ) ;
row [ 2 ] . normalize ( ) ;
values . skew [ 1 ] / = values . scale [ 2 ] ;
values . skew [ 2 ] / = values . 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.
auto pdum3 = row [ 1 ] . cross ( row [ 2 ] ) ;
if ( row [ 0 ] . dot ( pdum3 ) < 0.f ) {
for ( int i = 0 ; i < 3 ; i + + ) {
values . scale [ i ] * = - 1.f ;
row [ i ] [ 0 ] * = - 1.f ;
row [ i ] [ 1 ] * = - 1.f ;
row [ i ] [ 2 ] * = - 1.f ;
}
}
// Now, get the rotations out
values . rotation [ 0 ] = 0.5f * sqrt ( max ( 1.f + row [ 0 ] [ 0 ] - row [ 1 ] [ 1 ] - row [ 2 ] [ 2 ] , 0.f ) ) ;
values . rotation [ 1 ] = 0.5f * sqrt ( max ( 1.f - row [ 0 ] [ 0 ] + row [ 1 ] [ 1 ] - row [ 2 ] [ 2 ] , 0.f ) ) ;
values . rotation [ 2 ] = 0.5f * sqrt ( max ( 1.f - row [ 0 ] [ 0 ] - row [ 1 ] [ 1 ] + row [ 2 ] [ 2 ] , 0.f ) ) ;
values . rotation [ 3 ] = 0.5f * sqrt ( max ( 1.f + row [ 0 ] [ 0 ] + row [ 1 ] [ 1 ] + row [ 2 ] [ 2 ] , 0.f ) ) ;
if ( row [ 2 ] [ 1 ] > row [ 1 ] [ 2 ] )
values . rotation [ 0 ] = - values . rotation [ 0 ] ;
if ( row [ 0 ] [ 2 ] > row [ 2 ] [ 0 ] )
values . rotation [ 1 ] = - values . rotation [ 1 ] ;
if ( row [ 1 ] [ 0 ] > row [ 0 ] [ 1 ] )
values . rotation [ 2 ] = - values . rotation [ 2 ] ;
// FIXME: This accounts for the fact that the browser coordinate system is left-handed instead of right-handed.
// The reason for this is that the positive Y-axis direction points down instead of up. To fix this, we
// invert the Y axis. However, it feels like the spec pseudo-code above should have taken something like
// this into account, so we're probably doing something else wrong.
values . rotation [ 2 ] * = - 1 ;
return values ;
} ;
// https://drafts.csswg.org/css-transforms-2/#recomposing-to-a-3d-matrix
static constexpr auto recompose = [ ] ( DecomposedValues const & values ) - > FloatMatrix4x4 {
auto matrix = FloatMatrix4x4 : : identity ( ) ;
// apply perspective
for ( int i = 0 ; i < 4 ; i + + )
matrix ( 3 , i ) = values . perspective [ i ] ;
// apply translation
for ( int i = 0 ; i < 4 ; i + + ) {
for ( int j = 0 ; j < 3 ; j + + )
matrix ( i , 3 ) + = values . translation [ j ] * matrix ( i , j ) ;
}
// apply rotation
auto x = values . rotation [ 0 ] ;
auto y = values . rotation [ 1 ] ;
auto z = values . rotation [ 2 ] ;
auto w = values . rotation [ 3 ] ;
// Construct a composite rotation matrix from the quaternion values
// rotationMatrix is a identity 4x4 matrix initially
auto rotation_matrix = FloatMatrix4x4 : : identity ( ) ;
rotation_matrix ( 0 , 0 ) = 1.f - 2.f * ( y * y + z * z ) ;
rotation_matrix ( 1 , 0 ) = 2.f * ( x * y - z * w ) ;
rotation_matrix ( 2 , 0 ) = 2.f * ( x * z + y * w ) ;
rotation_matrix ( 0 , 1 ) = 2.f * ( x * y + z * w ) ;
rotation_matrix ( 1 , 1 ) = 1.f - 2.f * ( x * x + z * z ) ;
rotation_matrix ( 2 , 1 ) = 2.f * ( y * z - x * w ) ;
rotation_matrix ( 0 , 2 ) = 2.f * ( x * z - y * w ) ;
rotation_matrix ( 1 , 2 ) = 2.f * ( y * z + x * w ) ;
rotation_matrix ( 2 , 2 ) = 1.f - 2.f * ( x * x + y * y ) ;
matrix = matrix * rotation_matrix ;
// apply skew
// temp is a identity 4x4 matrix initially
auto temp = FloatMatrix4x4 : : identity ( ) ;
if ( values . skew [ 2 ] ! = 0.f ) {
temp ( 1 , 2 ) = values . skew [ 2 ] ;
matrix = matrix * temp ;
}
if ( values . skew [ 1 ] ! = 0.f ) {
temp ( 1 , 2 ) = 0.f ;
temp ( 0 , 2 ) = values . skew [ 1 ] ;
matrix = matrix * temp ;
}
if ( values . skew [ 0 ] ! = 0.f ) {
temp ( 0 , 2 ) = 0.f ;
temp ( 0 , 1 ) = values . skew [ 0 ] ;
matrix = matrix * temp ;
}
// apply scale
for ( int i = 0 ; i < 3 ; i + + ) {
for ( int j = 0 ; j < 4 ; j + + )
matrix ( j , i ) * = values . scale [ i ] ;
}
return matrix ;
} ;
// https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values
static constexpr auto interpolate = [ ] ( DecomposedValues & from , DecomposedValues & to , float delta ) - > DecomposedValues {
auto product = clamp ( from . rotation . dot ( to . rotation ) , - 1.0f , 1.0f ) ;
FloatVector4 interpolated_rotation ;
if ( fabsf ( product ) = = 1.0f ) {
interpolated_rotation = from . rotation ;
} else {
auto theta = acos ( product ) ;
auto w = sin ( delta * theta ) / sqrtf ( 1.0f - product * product ) ;
for ( int i = 0 ; i < 4 ; i + + ) {
from . rotation [ i ] * = cos ( delta * theta ) - product * w ;
to . rotation [ i ] * = w ;
interpolated_rotation [ i ] = from . rotation [ i ] + to . rotation [ i ] ;
}
}
return {
interpolate_raw ( from . translation , to . translation , delta ) ,
interpolate_raw ( from . scale , to . scale , delta ) ,
interpolate_raw ( from . skew , to . skew , delta ) ,
interpolated_rotation ,
interpolate_raw ( from . perspective , to . perspective , delta ) ,
} ;
} ;
auto from_matrix = style_value_to_matrix ( element , from ) ;
auto to_matrix = style_value_to_matrix ( element , to ) ;
auto from_decomposed = decompose ( from_matrix ) ;
auto to_decomposed = decompose ( to_matrix ) ;
if ( ! from_decomposed . has_value ( ) | | ! to_decomposed . has_value ( ) )
return { } ;
auto interpolated_decomposed = interpolate ( from_decomposed . value ( ) , to_decomposed . value ( ) , delta ) ;
auto interpolated = recompose ( interpolated_decomposed ) ;
StyleValueVector values ;
values . ensure_capacity ( 16 ) ;
for ( int i = 0 ; i < 16 ; i + + )
values . append ( NumberStyleValue : : create ( static_cast < double > ( interpolated ( i % 4 , i / 4 ) ) ) ) ;
2025-01-15 14:58:23 +00:00
return StyleValueList : : create ( { TransformationStyleValue : : create ( PropertyID : : Transform , TransformFunction : : Matrix3d , move ( values ) ) } , StyleValueList : : Separator : : Comma ) ;
2024-09-19 12:58:38 +01:00
}
Color interpolate_color ( Color from , Color to , float delta )
{
// https://drafts.csswg.org/css-color/#interpolation-space
// If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab.
auto from_oklab = from . to_oklab ( ) ;
auto to_oklab = to . to_oklab ( ) ;
auto color = Color : : from_oklab (
interpolate_raw ( from_oklab . L , to_oklab . L , delta ) ,
interpolate_raw ( from_oklab . a , to_oklab . a , delta ) ,
interpolate_raw ( from_oklab . b , to_oklab . b , delta ) ) ;
color . set_alpha ( interpolate_raw ( from . alpha ( ) , to . alpha ( ) , delta ) ) ;
return color ;
}
2025-01-08 16:14:17 +00:00
NonnullRefPtr < CSSStyleValue const > interpolate_box_shadow ( DOM : : Element & element , CalculationContext const & calculation_context , CSSStyleValue const & from , CSSStyleValue const & to , float delta )
2024-09-19 12:58:38 +01:00
{
// https://drafts.csswg.org/css-backgrounds/#box-shadow
// Animation type: by computed value, treating none as a zero-item list and appending blank shadows
// (transparent 0 0 0 0) with a corresponding inset keyword as needed to match the longer list if
// the shorter list is otherwise compatible with the longer one
static constexpr auto process_list = [ ] ( CSSStyleValue const & value ) {
StyleValueVector shadows ;
if ( value . is_value_list ( ) ) {
for ( auto const & element : value . as_value_list ( ) . values ( ) ) {
if ( element - > is_shadow ( ) )
shadows . append ( element ) ;
}
} else if ( value . is_shadow ( ) ) {
shadows . append ( value ) ;
} else if ( ! value . is_keyword ( ) | | value . as_keyword ( ) . keyword ( ) ! = Keyword : : None ) {
VERIFY_NOT_REACHED ( ) ;
}
return shadows ;
} ;
static constexpr auto extend_list_if_necessary = [ ] ( StyleValueVector & values , StyleValueVector const & other ) {
values . ensure_capacity ( other . size ( ) ) ;
for ( size_t i = values . size ( ) ; i < other . size ( ) ; i + + ) {
values . unchecked_append ( ShadowStyleValue : : create (
2025-02-19 21:02:12 +11:00
CSSColorValue : : create_from_color ( Color : : Transparent , ColorSyntax : : Legacy ) ,
2024-09-19 12:58:38 +01:00
LengthStyleValue : : create ( Length : : make_px ( 0 ) ) ,
LengthStyleValue : : create ( Length : : make_px ( 0 ) ) ,
LengthStyleValue : : create ( Length : : make_px ( 0 ) ) ,
LengthStyleValue : : create ( Length : : make_px ( 0 ) ) ,
other [ i ] - > as_shadow ( ) . placement ( ) ) ) ;
}
} ;
StyleValueVector from_shadows = process_list ( from ) ;
StyleValueVector to_shadows = process_list ( to ) ;
extend_list_if_necessary ( from_shadows , to_shadows ) ;
extend_list_if_necessary ( to_shadows , from_shadows ) ;
VERIFY ( from_shadows . size ( ) = = to_shadows . size ( ) ) ;
StyleValueVector result_shadows ;
result_shadows . ensure_capacity ( from_shadows . size ( ) ) ;
for ( size_t i = 0 ; i < from_shadows . size ( ) ; i + + ) {
auto const & from_shadow = from_shadows [ i ] - > as_shadow ( ) ;
auto const & to_shadow = to_shadows [ i ] - > as_shadow ( ) ;
auto result_shadow = ShadowStyleValue : : create (
2025-02-19 21:02:12 +11:00
CSSColorValue : : create_from_color ( interpolate_color ( from_shadow . color ( ) - > to_color ( { } ) , to_shadow . color ( ) - > to_color ( { } ) , delta ) , ColorSyntax : : Modern ) ,
2025-01-08 16:14:17 +00:00
interpolate_value ( element , calculation_context , from_shadow . offset_x ( ) , to_shadow . offset_x ( ) , delta ) ,
interpolate_value ( element , calculation_context , from_shadow . offset_y ( ) , to_shadow . offset_y ( ) , delta ) ,
interpolate_value ( element , calculation_context , from_shadow . blur_radius ( ) , to_shadow . blur_radius ( ) , delta ) ,
interpolate_value ( element , calculation_context , from_shadow . spread_distance ( ) , to_shadow . spread_distance ( ) , delta ) ,
2024-09-19 12:58:38 +01:00
delta > = 0.5f ? to_shadow . placement ( ) : from_shadow . placement ( ) ) ;
result_shadows . unchecked_append ( result_shadow ) ;
}
return StyleValueList : : create ( move ( result_shadows ) , StyleValueList : : Separator : : Comma ) ;
}
2025-01-08 16:14:17 +00:00
NonnullRefPtr < CSSStyleValue const > interpolate_value ( DOM : : Element & element , CalculationContext const & calculation_context , CSSStyleValue const & from , CSSStyleValue const & to , float delta )
2024-09-19 12:58:38 +01:00
{
if ( from . type ( ) ! = to . type ( ) ) {
// Handle mixed percentage and dimension types
// https://www.w3.org/TR/css-values-4/#mixed-percentages
struct NumericBaseTypeAndDefault {
CSSNumericType : : BaseType base_type ;
ValueComparingNonnullRefPtr < CSSStyleValue > default_value ;
} ;
static constexpr auto numeric_base_type_and_default = [ ] ( CSSStyleValue const & value ) - > Optional < NumericBaseTypeAndDefault > {
switch ( value . type ( ) ) {
case CSSStyleValue : : Type : : Angle : {
static auto default_angle_value = AngleStyleValue : : create ( Angle : : make_degrees ( 0 ) ) ;
return NumericBaseTypeAndDefault { CSSNumericType : : BaseType : : Angle , default_angle_value } ;
}
case CSSStyleValue : : Type : : Frequency : {
static auto default_frequency_value = FrequencyStyleValue : : create ( Frequency : : make_hertz ( 0 ) ) ;
return NumericBaseTypeAndDefault { CSSNumericType : : BaseType : : Frequency , default_frequency_value } ;
}
case CSSStyleValue : : Type : : Length : {
static auto default_length_value = LengthStyleValue : : create ( Length : : make_px ( 0 ) ) ;
return NumericBaseTypeAndDefault { CSSNumericType : : BaseType : : Length , default_length_value } ;
}
case CSSStyleValue : : Type : : Percentage : {
static auto default_percentage_value = PercentageStyleValue : : create ( Percentage { 0.0 } ) ;
return NumericBaseTypeAndDefault { CSSNumericType : : BaseType : : Percent , default_percentage_value } ;
}
case CSSStyleValue : : Type : : Time : {
static auto default_time_value = TimeStyleValue : : create ( Time : : make_seconds ( 0 ) ) ;
return NumericBaseTypeAndDefault { CSSNumericType : : BaseType : : Time , default_time_value } ;
}
default :
return { } ;
}
} ;
2025-01-22 16:50:54 +00:00
static auto to_calculation_node = [ calculation_context ] ( CSSStyleValue const & value ) - > NonnullRefPtr < CalculationNode > {
2024-09-19 12:58:38 +01:00
switch ( value . type ( ) ) {
case CSSStyleValue : : Type : : Angle :
2025-01-08 16:14:17 +00:00
return NumericCalculationNode : : create ( value . as_angle ( ) . angle ( ) , calculation_context ) ;
2024-09-19 12:58:38 +01:00
case CSSStyleValue : : Type : : Frequency :
2025-01-08 16:14:17 +00:00
return NumericCalculationNode : : create ( value . as_frequency ( ) . frequency ( ) , calculation_context ) ;
2024-09-19 12:58:38 +01:00
case CSSStyleValue : : Type : : Length :
2025-01-08 16:14:17 +00:00
return NumericCalculationNode : : create ( value . as_length ( ) . length ( ) , calculation_context ) ;
2024-09-19 12:58:38 +01:00
case CSSStyleValue : : Type : : Percentage :
2025-01-08 16:14:17 +00:00
return NumericCalculationNode : : create ( value . as_percentage ( ) . percentage ( ) , calculation_context ) ;
2024-09-19 12:58:38 +01:00
case CSSStyleValue : : Type : : Time :
2025-01-08 16:14:17 +00:00
return NumericCalculationNode : : create ( value . as_time ( ) . time ( ) , calculation_context ) ;
2024-09-19 12:58:38 +01:00
default :
VERIFY_NOT_REACHED ( ) ;
}
} ;
auto from_base_type_and_default = numeric_base_type_and_default ( from ) ;
auto to_base_type_and_default = numeric_base_type_and_default ( to ) ;
if ( from_base_type_and_default . has_value ( ) & & to_base_type_and_default . has_value ( ) & & ( from_base_type_and_default - > base_type = = CSSNumericType : : BaseType : : Percent | | to_base_type_and_default - > base_type = = CSSNumericType : : BaseType : : Percent ) ) {
// This is an interpolation from a numeric unit to a percentage, or vice versa. The trick here is to
// interpolate two separate values. For example, consider an interpolation from 30px to 80%. It's quite
// hard to understand how this interpolation works, but if instead we rewrite the values as "30px + 0%" and
// "0px + 80%", then it is very simple to understand; we just interpolate each component separately.
2025-01-08 16:14:17 +00:00
auto interpolated_from = interpolate_value ( element , calculation_context , from , from_base_type_and_default - > default_value , delta ) ;
auto interpolated_to = interpolate_value ( element , calculation_context , to_base_type_and_default - > default_value , to , delta ) ;
2024-09-19 12:58:38 +01:00
2025-01-22 16:50:54 +00:00
Vector < NonnullRefPtr < CalculationNode > > values ;
2024-09-19 12:58:38 +01:00
values . ensure_capacity ( 2 ) ;
values . unchecked_append ( to_calculation_node ( interpolated_from ) ) ;
values . unchecked_append ( to_calculation_node ( interpolated_to ) ) ;
auto calc_node = SumCalculationNode : : create ( move ( values ) ) ;
2025-01-08 16:14:17 +00:00
return CalculatedStyleValue : : create ( move ( calc_node ) , CSSNumericType { to_base_type_and_default - > base_type , 1 } , calculation_context ) ;
2024-09-19 12:58:38 +01:00
}
return delta > = 0.5f ? to : from ;
}
switch ( from . type ( ) ) {
case CSSStyleValue : : Type : : Angle :
return AngleStyleValue : : create ( Angle : : make_degrees ( interpolate_raw ( from . as_angle ( ) . angle ( ) . to_degrees ( ) , to . as_angle ( ) . angle ( ) . to_degrees ( ) , delta ) ) ) ;
case CSSStyleValue : : Type : : Color : {
Optional < Layout : : NodeWithStyle const & > layout_node ;
if ( auto node = element . layout_node ( ) )
layout_node = * node ;
2025-02-19 21:02:12 +11:00
return CSSColorValue : : create_from_color ( interpolate_color ( from . to_color ( layout_node ) , to . to_color ( layout_node ) , delta ) , ColorSyntax : : Modern ) ;
2024-09-19 12:58:38 +01:00
}
case CSSStyleValue : : Type : : Integer :
return IntegerStyleValue : : create ( interpolate_raw ( from . as_integer ( ) . integer ( ) , to . as_integer ( ) . integer ( ) , delta ) ) ;
case CSSStyleValue : : Type : : Length : {
2024-11-25 13:51:19 +01:00
// FIXME: Absolutize values
auto const & from_length = from . as_length ( ) . length ( ) ;
auto const & to_length = to . as_length ( ) . length ( ) ;
2024-09-19 12:58:38 +01:00
return LengthStyleValue : : create ( Length ( interpolate_raw ( from_length . raw_value ( ) , to_length . raw_value ( ) , delta ) , from_length . type ( ) ) ) ;
}
case CSSStyleValue : : Type : : Number :
return NumberStyleValue : : create ( interpolate_raw ( from . as_number ( ) . number ( ) , to . as_number ( ) . number ( ) , delta ) ) ;
case CSSStyleValue : : Type : : Percentage :
return PercentageStyleValue : : create ( Percentage ( interpolate_raw ( from . as_percentage ( ) . percentage ( ) . value ( ) , to . as_percentage ( ) . percentage ( ) . value ( ) , delta ) ) ) ;
case CSSStyleValue : : Type : : Position : {
// https://www.w3.org/TR/css-values-4/#combine-positions
// FIXME: Interpolation of <position> is defined as the independent interpolation of each component (x, y) normalized as an offset from the top left corner as a <length-percentage>.
2024-11-25 13:51:19 +01:00
auto const & from_position = from . as_position ( ) ;
auto const & to_position = to . as_position ( ) ;
2024-09-19 12:58:38 +01:00
return PositionStyleValue : : create (
2025-01-08 16:14:17 +00:00
interpolate_value ( element , calculation_context , from_position . edge_x ( ) , to_position . edge_x ( ) , delta ) - > as_edge ( ) ,
interpolate_value ( element , calculation_context , from_position . edge_y ( ) , to_position . edge_y ( ) , delta ) - > as_edge ( ) ) ;
2024-09-19 12:58:38 +01:00
}
case CSSStyleValue : : Type : : Ratio : {
auto from_ratio = from . as_ratio ( ) . ratio ( ) ;
auto to_ratio = to . as_ratio ( ) . ratio ( ) ;
// The interpolation of a <ratio> is defined by converting each <ratio> to a number by dividing the first value
// by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would
// become approximately 0.176), then interpolating those values. The result during the interpolation is
// converted back to a <ratio> by inverting the logarithm, then interpreting the result as a <ratio> with the
// result as the first value and 1 as the second value.
auto from_number = log ( from_ratio . value ( ) ) ;
auto to_number = log ( to_ratio . value ( ) ) ;
auto interp_number = interpolate_raw ( from_number , to_number , delta ) ;
return RatioStyleValue : : create ( Ratio ( pow ( M_E , interp_number ) ) ) ;
}
case CSSStyleValue : : Type : : Rect : {
auto from_rect = from . as_rect ( ) . rect ( ) ;
auto to_rect = to . as_rect ( ) . rect ( ) ;
2024-11-25 13:51:19 +01:00
if ( from_rect . top_edge . is_auto ( ) ! = to_rect . top_edge . is_auto ( ) | | from_rect . right_edge . is_auto ( ) ! = to_rect . right_edge . is_auto ( ) | | from_rect . bottom_edge . is_auto ( ) ! = to_rect . bottom_edge . is_auto ( ) | | from_rect . left_edge . is_auto ( ) ! = to_rect . left_edge . is_auto ( ) ) {
return delta > = 0.5f ? to : from ;
}
// FIXME: Absolutize values
2024-09-19 12:58:38 +01:00
return RectStyleValue : : create ( {
Length ( interpolate_raw ( from_rect . top_edge . raw_value ( ) , to_rect . top_edge . raw_value ( ) , delta ) , from_rect . top_edge . type ( ) ) ,
Length ( interpolate_raw ( from_rect . right_edge . raw_value ( ) , to_rect . right_edge . raw_value ( ) , delta ) , from_rect . right_edge . type ( ) ) ,
Length ( interpolate_raw ( from_rect . bottom_edge . raw_value ( ) , to_rect . bottom_edge . raw_value ( ) , delta ) , from_rect . bottom_edge . type ( ) ) ,
Length ( interpolate_raw ( from_rect . left_edge . raw_value ( ) , to_rect . left_edge . raw_value ( ) , delta ) , from_rect . left_edge . type ( ) ) ,
} ) ;
}
case CSSStyleValue : : Type : : Transformation :
VERIFY_NOT_REACHED ( ) ;
case CSSStyleValue : : Type : : ValueList : {
2024-11-25 13:51:19 +01:00
auto const & from_list = from . as_value_list ( ) ;
auto const & to_list = to . as_value_list ( ) ;
2024-09-19 12:58:38 +01:00
if ( from_list . size ( ) ! = to_list . size ( ) )
2024-11-25 13:51:19 +01:00
return delta > = 0.5f ? to : from ;
2024-09-19 12:58:38 +01:00
2024-11-25 13:51:19 +01:00
// FIXME: If the number of components or the types of corresponding components do not match,
// or if any component value uses discrete animation and the two corresponding values do not match,
// then the property values combine as discrete.
2024-09-19 12:58:38 +01:00
StyleValueVector interpolated_values ;
interpolated_values . ensure_capacity ( from_list . size ( ) ) ;
for ( size_t i = 0 ; i < from_list . size ( ) ; + + i )
2025-01-08 16:14:17 +00:00
interpolated_values . append ( interpolate_value ( element , calculation_context , from_list . values ( ) [ i ] , to_list . values ( ) [ i ] , delta ) ) ;
2024-09-19 12:58:38 +01:00
return StyleValueList : : create ( move ( interpolated_values ) , from_list . separator ( ) ) ;
}
default :
2024-11-25 13:51:19 +01:00
return delta > = 0.5f ? to : from ;
2024-09-19 12:58:38 +01:00
}
}
}