mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Implement CSS perspective property
This commit is contained in:
parent
e44a97e508
commit
eb21ea890c
Notes:
github-actions[bot]
2025-11-11 23:42:45 +00:00
Author: https://github.com/Psychpsyo
Commit: eb21ea890c
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6776
Reviewed-by: https://github.com/gmta ✅
13 changed files with 108 additions and 2 deletions
|
|
@ -714,6 +714,20 @@ TransformBox ComputedProperties::transform_box() const
|
|||
return keyword_to_transform_box(value.to_keyword()).release_value();
|
||||
}
|
||||
|
||||
Optional<CSSPixels> ComputedProperties::perspective() const
|
||||
{
|
||||
auto const& value = property(PropertyID::Perspective);
|
||||
if (value.is_keyword() && value.to_keyword() == Keyword::None)
|
||||
return {};
|
||||
|
||||
if (value.is_length())
|
||||
return value.as_length().length().absolute_length_to_px();
|
||||
if (value.is_calculated())
|
||||
return value.as_calculated().resolve_length({ .length_resolution_context = {} })->absolute_length_to_px();
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
TransformOrigin ComputedProperties::transform_origin() const
|
||||
{
|
||||
auto length_percentage_with_keywords_resolved = [](StyleValue const& value) -> LengthPercentage {
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ public:
|
|||
Optional<Transformation> rotate() const;
|
||||
Optional<Transformation> translate() const;
|
||||
Optional<Transformation> scale() const;
|
||||
Optional<CSSPixels> perspective() const;
|
||||
|
||||
MaskType mask_type() const;
|
||||
float stop_opacity() const;
|
||||
|
|
|
|||
|
|
@ -637,6 +637,7 @@ public:
|
|||
Optional<Transformation> const& rotate() const { return m_noninherited.rotate; }
|
||||
Optional<Transformation> const& translate() const { return m_noninherited.translate; }
|
||||
Optional<Transformation> const& scale() const { return m_noninherited.scale; }
|
||||
Optional<CSSPixels> const& perspective() const { return m_noninherited.perspective; }
|
||||
|
||||
Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; }
|
||||
CSSPixels font_size() const { return m_inherited.font_size; }
|
||||
|
|
@ -838,6 +839,7 @@ protected:
|
|||
Optional<Transformation> rotate;
|
||||
Optional<Transformation> translate;
|
||||
Optional<Transformation> scale;
|
||||
Optional<CSSPixels> perspective;
|
||||
|
||||
Optional<MaskReference> mask;
|
||||
MaskType mask_type { InitialValues::mask_type() };
|
||||
|
|
@ -990,6 +992,7 @@ public:
|
|||
void set_box_shadow(Vector<ShadowData>&& value) { m_noninherited.box_shadow = move(value); }
|
||||
void set_rotate(Transformation value) { m_noninherited.rotate = move(value); }
|
||||
void set_scale(Transformation value) { m_noninherited.scale = move(value); }
|
||||
void set_perspective(Optional<CSSPixels> value) { m_noninherited.perspective = move(value); }
|
||||
void set_transformations(Vector<Transformation> value) { m_noninherited.transformations = move(value); }
|
||||
void set_transform_box(TransformBox value) { m_noninherited.transform_box = value; }
|
||||
void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = move(value); }
|
||||
|
|
|
|||
|
|
@ -3215,6 +3215,17 @@
|
|||
"paint-order"
|
||||
]
|
||||
},
|
||||
"perspective": {
|
||||
"animation-type": "by-computed-value",
|
||||
"inherited": false,
|
||||
"initial": "none",
|
||||
"valid-identifiers": [
|
||||
"none"
|
||||
],
|
||||
"valid-types": [
|
||||
"length [0,∞]"
|
||||
]
|
||||
},
|
||||
"place-content": {
|
||||
"inherited": false,
|
||||
"initial": "normal",
|
||||
|
|
|
|||
|
|
@ -98,6 +98,12 @@ bool Node::can_contain_boxes_with_position_absolute() const
|
|||
if (computed_values().scale().has_value())
|
||||
return true;
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-2/#propdef-perspective
|
||||
// The use of this property with any value other than 'none' establishes a stacking context. It also establishes
|
||||
// a containing block for all descendants, just like the 'transform' property does.
|
||||
if (computed_values().perspective().has_value())
|
||||
return true;
|
||||
|
||||
// https://drafts.csswg.org/css-contain-2/#containment-types
|
||||
// 4. The layout containment box establishes an absolute positioning containing block and a fixed positioning
|
||||
// containing block.
|
||||
|
|
@ -284,6 +290,11 @@ bool Node::establishes_stacking_context() const
|
|||
if (computed_values.view_transition_name().has_value() || will_change_property(CSS::PropertyID::ViewTransitionName))
|
||||
return true;
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-2/#propdef-perspective
|
||||
// The use of this property with any value other than 'none' establishes a stacking context.
|
||||
if (computed_values.perspective().has_value() || will_change_property(CSS::PropertyID::Perspective))
|
||||
return true;
|
||||
|
||||
return computed_values.opacity() < 1.0f || will_change_property(CSS::PropertyID::Opacity);
|
||||
}
|
||||
|
||||
|
|
@ -682,6 +693,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
|
|||
computed_values.set_transformations(computed_style.transformations());
|
||||
computed_values.set_transform_box(computed_style.transform_box());
|
||||
computed_values.set_transform_origin(computed_style.transform_origin());
|
||||
computed_values.set_perspective(computed_style.perspective());
|
||||
|
||||
auto const& transition_delay_property = computed_style.property(CSS::PropertyID::TransitionDelay);
|
||||
if (transition_delay_property.is_time()) {
|
||||
|
|
|
|||
|
|
@ -1535,6 +1535,35 @@ void PaintableBox::resolve_paint_properties()
|
|||
matrix = matrix * transform.to_matrix(*this).release_value();
|
||||
set_transform(matrix);
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-2/#perspective
|
||||
auto const& perspective = computed_values.perspective();
|
||||
if (perspective.has_value()) {
|
||||
// The perspective matrix is computed as follows:
|
||||
|
||||
// 1. Start with the identity matrix.
|
||||
m_perspective_matrix = Gfx::FloatMatrix4x4::identity();
|
||||
|
||||
// 2. Translate by the computed X and Y values of 'perspective-origin'
|
||||
// FIXME: Implement this.
|
||||
|
||||
// 3. Multiply by the matrix that would be obtained from the 'perspective()' transform function, where the
|
||||
// length is provided by the value of the perspective property
|
||||
auto perspective_value = perspective.value();
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-2/#perspective-property
|
||||
// As very small <length> values can produce bizarre rendering results and stress the numerical accuracy
|
||||
// of transform calculations, values less than '1px' must be treated as '1px' for rendering purposes. (This
|
||||
// clamping does not affect the underlying value, so 'perspective: 0;' in a stylesheet will still serialize back
|
||||
// as '0'.)
|
||||
if (perspective_value < 1)
|
||||
perspective_value = 1;
|
||||
|
||||
m_perspective_matrix = m_perspective_matrix * CSS::Transformation(CSS::TransformFunction::Perspective, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(perspective_value)) })).to_matrix(*this).release_value();
|
||||
|
||||
// 4. Translate by the negated computed X and Y values of 'perspective-origin'
|
||||
// FIXME: Implement this.
|
||||
}
|
||||
|
||||
auto const& transform_origin = computed_values.transform_origin();
|
||||
auto reference_box = transform_box_rect();
|
||||
auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width());
|
||||
|
|
|
|||
|
|
@ -201,6 +201,9 @@ public:
|
|||
void set_transform(Gfx::FloatMatrix4x4 transform) { m_transform = transform; }
|
||||
Gfx::FloatMatrix4x4 const& transform() const { return m_transform; }
|
||||
|
||||
void set_perspective_matrix(Gfx::FloatMatrix4x4 perspective_matrix) { m_perspective_matrix = perspective_matrix; }
|
||||
Gfx::FloatMatrix4x4 const& perspective_matrix() const { return m_perspective_matrix; }
|
||||
|
||||
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
|
||||
CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
|
||||
|
||||
|
|
@ -328,6 +331,7 @@ private:
|
|||
BorderRadiiData m_border_radii_data;
|
||||
Vector<ShadowData> m_box_shadow_data;
|
||||
Gfx::FloatMatrix4x4 m_transform { Gfx::FloatMatrix4x4::identity() };
|
||||
Gfx::FloatMatrix4x4 m_perspective_matrix { Gfx::FloatMatrix4x4::identity() };
|
||||
CSSPixelPoint m_transform_origin;
|
||||
|
||||
Optional<BordersData> m_outline_data;
|
||||
|
|
|
|||
|
|
@ -306,6 +306,13 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
|
|||
auto source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>();
|
||||
|
||||
auto transform_matrix = paintable_box().transform();
|
||||
// https://drafts.csswg.org/css-transforms-2/#perspective
|
||||
// Second, the 'perspective' and 'perspective-origin' properties can be applied to an element to influence the
|
||||
// rendering of its 3d-transformed children, giving them a shared perspective that provides the impression of
|
||||
// them living in the same three-dimensional scene.
|
||||
if (auto const* parent = as_if<PaintableBox>(paintable_box().parent()))
|
||||
transform_matrix = parent->perspective_matrix() * transform_matrix;
|
||||
|
||||
auto transform_origin = paintable_box().transform_origin().to_type<float>();
|
||||
|
||||
Gfx::CompositingAndBlendingOperator compositing_and_blending_operator = mix_blend_mode_to_compositing_and_blending_operator(paintable_box().computed_values().mix_blend_mode());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Test (Transforms): rotatex() and 'perspective'</title>
|
||||
<link rel="author" title="Matt Woodrow" href="mailto:mwoodrow@mozilla.com">
|
||||
<link rel="author" title="Aryeh Gregor" href="mailto:ayg@aryeh.name">
|
||||
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#three-d-transform-functions">
|
||||
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#funcdef-rotatex">
|
||||
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#perspective-property">
|
||||
<meta name="assert" content="This tests that 'perspective' has some effect
|
||||
when combined with rotatex() (i.e., is not equivalent to simply omitting
|
||||
the perspective).">
|
||||
<link rel="mismatch" href="../../../../expected/wpt-import/css/css-transforms/transform3d-rotatex-ref.html">
|
||||
</head>
|
||||
<body>
|
||||
<div style="perspective: 1000px;">
|
||||
<div style="transform: rotatex(45deg); width: 100px; height: 100px;
|
||||
background: lime"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -244,6 +244,7 @@ All properties associated with getComputedStyle(document.body):
|
|||
"padding-left",
|
||||
"padding-right",
|
||||
"padding-top",
|
||||
"perspective",
|
||||
"position",
|
||||
"position-anchor",
|
||||
"position-area",
|
||||
|
|
|
|||
|
|
@ -663,6 +663,7 @@ All supported properties and their default values exposed from CSSStylePropertie
|
|||
'padding-top': '0px'
|
||||
'paintOrder': 'normal'
|
||||
'paint-order': 'normal'
|
||||
'perspective': 'none'
|
||||
'placeContent': 'normal'
|
||||
'place-content': 'normal'
|
||||
'placeItems': 'normal legacy'
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ padding-inline-start: 0px
|
|||
padding-left: 0px
|
||||
padding-right: 0px
|
||||
padding-top: 0px
|
||||
perspective: none
|
||||
position: static
|
||||
position-anchor: auto
|
||||
position-area: none
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 269 tests
|
||||
Found 270 tests
|
||||
|
||||
263 Pass
|
||||
264 Pass
|
||||
6 Fail
|
||||
Pass accent-color
|
||||
Pass border-collapse
|
||||
|
|
@ -231,6 +231,7 @@ Pass padding-inline-start
|
|||
Pass padding-left
|
||||
Pass padding-right
|
||||
Pass padding-top
|
||||
Pass perspective
|
||||
Pass position
|
||||
Pass r
|
||||
Pass right
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue