Skip to content

Commit eb21ea8

Browse files
Psychpsyogmta
authored andcommitted
LibWeb: Implement CSS perspective property
1 parent e44a97e commit eb21ea8

File tree

13 files changed

+108
-2
lines changed

13 files changed

+108
-2
lines changed

Libraries/LibWeb/CSS/ComputedProperties.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,20 @@ TransformBox ComputedProperties::transform_box() const
714714
return keyword_to_transform_box(value.to_keyword()).release_value();
715715
}
716716

717+
Optional<CSSPixels> ComputedProperties::perspective() const
718+
{
719+
auto const& value = property(PropertyID::Perspective);
720+
if (value.is_keyword() && value.to_keyword() == Keyword::None)
721+
return {};
722+
723+
if (value.is_length())
724+
return value.as_length().length().absolute_length_to_px();
725+
if (value.is_calculated())
726+
return value.as_calculated().resolve_length({ .length_resolution_context = {} })->absolute_length_to_px();
727+
728+
VERIFY_NOT_REACHED();
729+
}
730+
717731
TransformOrigin ComputedProperties::transform_origin() const
718732
{
719733
auto length_percentage_with_keywords_resolved = [](StyleValue const& value) -> LengthPercentage {

Libraries/LibWeb/CSS/ComputedProperties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ class WEB_API ComputedProperties final : public JS::Cell {
210210
Optional<Transformation> rotate() const;
211211
Optional<Transformation> translate() const;
212212
Optional<Transformation> scale() const;
213+
Optional<CSSPixels> perspective() const;
213214

214215
MaskType mask_type() const;
215216
float stop_opacity() const;

Libraries/LibWeb/CSS/ComputedValues.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,7 @@ class ComputedValues {
637637
Optional<Transformation> const& rotate() const { return m_noninherited.rotate; }
638638
Optional<Transformation> const& translate() const { return m_noninherited.translate; }
639639
Optional<Transformation> const& scale() const { return m_noninherited.scale; }
640+
Optional<CSSPixels> const& perspective() const { return m_noninherited.perspective; }
640641

641642
Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; }
642643
CSSPixels font_size() const { return m_inherited.font_size; }
@@ -838,6 +839,7 @@ class ComputedValues {
838839
Optional<Transformation> rotate;
839840
Optional<Transformation> translate;
840841
Optional<Transformation> scale;
842+
Optional<CSSPixels> perspective;
841843

842844
Optional<MaskReference> mask;
843845
MaskType mask_type { InitialValues::mask_type() };
@@ -990,6 +992,7 @@ class MutableComputedValues final : public ComputedValues {
990992
void set_box_shadow(Vector<ShadowData>&& value) { m_noninherited.box_shadow = move(value); }
991993
void set_rotate(Transformation value) { m_noninherited.rotate = move(value); }
992994
void set_scale(Transformation value) { m_noninherited.scale = move(value); }
995+
void set_perspective(Optional<CSSPixels> value) { m_noninherited.perspective = move(value); }
993996
void set_transformations(Vector<Transformation> value) { m_noninherited.transformations = move(value); }
994997
void set_transform_box(TransformBox value) { m_noninherited.transform_box = value; }
995998
void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = move(value); }

Libraries/LibWeb/CSS/Properties.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3215,6 +3215,17 @@
32153215
"paint-order"
32163216
]
32173217
},
3218+
"perspective": {
3219+
"animation-type": "by-computed-value",
3220+
"inherited": false,
3221+
"initial": "none",
3222+
"valid-identifiers": [
3223+
"none"
3224+
],
3225+
"valid-types": [
3226+
"length [0,∞]"
3227+
]
3228+
},
32183229
"place-content": {
32193230
"inherited": false,
32203231
"initial": "normal",

Libraries/LibWeb/Layout/Node.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ bool Node::can_contain_boxes_with_position_absolute() const
9898
if (computed_values().scale().has_value())
9999
return true;
100100

101+
// https://drafts.csswg.org/css-transforms-2/#propdef-perspective
102+
// The use of this property with any value other than 'none' establishes a stacking context. It also establishes
103+
// a containing block for all descendants, just like the 'transform' property does.
104+
if (computed_values().perspective().has_value())
105+
return true;
106+
101107
// https://drafts.csswg.org/css-contain-2/#containment-types
102108
// 4. The layout containment box establishes an absolute positioning containing block and a fixed positioning
103109
// containing block.
@@ -284,6 +290,11 @@ bool Node::establishes_stacking_context() const
284290
if (computed_values.view_transition_name().has_value() || will_change_property(CSS::PropertyID::ViewTransitionName))
285291
return true;
286292

293+
// https://drafts.csswg.org/css-transforms-2/#propdef-perspective
294+
// The use of this property with any value other than 'none' establishes a stacking context.
295+
if (computed_values.perspective().has_value() || will_change_property(CSS::PropertyID::Perspective))
296+
return true;
297+
287298
return computed_values.opacity() < 1.0f || will_change_property(CSS::PropertyID::Opacity);
288299
}
289300

@@ -682,6 +693,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
682693
computed_values.set_transformations(computed_style.transformations());
683694
computed_values.set_transform_box(computed_style.transform_box());
684695
computed_values.set_transform_origin(computed_style.transform_origin());
696+
computed_values.set_perspective(computed_style.perspective());
685697

686698
auto const& transition_delay_property = computed_style.property(CSS::PropertyID::TransitionDelay);
687699
if (transition_delay_property.is_time()) {

Libraries/LibWeb/Painting/PaintableBox.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,35 @@ void PaintableBox::resolve_paint_properties()
15351535
matrix = matrix * transform.to_matrix(*this).release_value();
15361536
set_transform(matrix);
15371537

1538+
// https://drafts.csswg.org/css-transforms-2/#perspective
1539+
auto const& perspective = computed_values.perspective();
1540+
if (perspective.has_value()) {
1541+
// The perspective matrix is computed as follows:
1542+
1543+
// 1. Start with the identity matrix.
1544+
m_perspective_matrix = Gfx::FloatMatrix4x4::identity();
1545+
1546+
// 2. Translate by the computed X and Y values of 'perspective-origin'
1547+
// FIXME: Implement this.
1548+
1549+
// 3. Multiply by the matrix that would be obtained from the 'perspective()' transform function, where the
1550+
// length is provided by the value of the perspective property
1551+
auto perspective_value = perspective.value();
1552+
1553+
// https://drafts.csswg.org/css-transforms-2/#perspective-property
1554+
// As very small <length> values can produce bizarre rendering results and stress the numerical accuracy
1555+
// of transform calculations, values less than '1px' must be treated as '1px' for rendering purposes. (This
1556+
// clamping does not affect the underlying value, so 'perspective: 0;' in a stylesheet will still serialize back
1557+
// as '0'.)
1558+
if (perspective_value < 1)
1559+
perspective_value = 1;
1560+
1561+
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();
1562+
1563+
// 4. Translate by the negated computed X and Y values of 'perspective-origin'
1564+
// FIXME: Implement this.
1565+
}
1566+
15381567
auto const& transform_origin = computed_values.transform_origin();
15391568
auto reference_box = transform_box_rect();
15401569
auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width());

Libraries/LibWeb/Painting/PaintableBox.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ class WEB_API PaintableBox : public Paintable {
201201
void set_transform(Gfx::FloatMatrix4x4 transform) { m_transform = transform; }
202202
Gfx::FloatMatrix4x4 const& transform() const { return m_transform; }
203203

204+
void set_perspective_matrix(Gfx::FloatMatrix4x4 perspective_matrix) { m_perspective_matrix = perspective_matrix; }
205+
Gfx::FloatMatrix4x4 const& perspective_matrix() const { return m_perspective_matrix; }
206+
204207
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
205208
CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
206209

@@ -328,6 +331,7 @@ class WEB_API PaintableBox : public Paintable {
328331
BorderRadiiData m_border_radii_data;
329332
Vector<ShadowData> m_box_shadow_data;
330333
Gfx::FloatMatrix4x4 m_transform { Gfx::FloatMatrix4x4::identity() };
334+
Gfx::FloatMatrix4x4 m_perspective_matrix { Gfx::FloatMatrix4x4::identity() };
331335
CSSPixelPoint m_transform_origin;
332336

333337
Optional<BordersData> m_outline_data;

Libraries/LibWeb/Painting/StackingContext.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
306306
auto source_paintable_rect = context.enclosing_device_rect(paintable_box().absolute_paint_rect()).to_type<int>();
307307

308308
auto transform_matrix = paintable_box().transform();
309+
// https://drafts.csswg.org/css-transforms-2/#perspective
310+
// Second, the 'perspective' and 'perspective-origin' properties can be applied to an element to influence the
311+
// rendering of its 3d-transformed children, giving them a shared perspective that provides the impression of
312+
// them living in the same three-dimensional scene.
313+
if (auto const* parent = as_if<PaintableBox>(paintable_box().parent()))
314+
transform_matrix = parent->perspective_matrix() * transform_matrix;
315+
309316
auto transform_origin = paintable_box().transform_origin().to_type<float>();
310317

311318
Gfx::CompositingAndBlendingOperator compositing_and_blending_operator = mix_blend_mode_to_compositing_and_blending_operator(paintable_box().computed_values().mix_blend_mode());
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>CSS Test (Transforms): rotatex() and 'perspective'</title>
5+
<link rel="author" title="Matt Woodrow" href="mailto:mwoodrow@mozilla.com">
6+
<link rel="author" title="Aryeh Gregor" href="mailto:ayg@aryeh.name">
7+
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#three-d-transform-functions">
8+
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#funcdef-rotatex">
9+
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#perspective-property">
10+
<meta name="assert" content="This tests that 'perspective' has some effect
11+
when combined with rotatex() (i.e., is not equivalent to simply omitting
12+
the perspective).">
13+
<link rel="mismatch" href="../../../../expected/wpt-import/css/css-transforms/transform3d-rotatex-ref.html">
14+
</head>
15+
<body>
16+
<div style="perspective: 1000px;">
17+
<div style="transform: rotatex(45deg); width: 100px; height: 100px;
18+
background: lime"></div>
19+
</div>
20+
</body>
21+
</html>

Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ All properties associated with getComputedStyle(document.body):
244244
"padding-left",
245245
"padding-right",
246246
"padding-top",
247+
"perspective",
247248
"position",
248249
"position-anchor",
249250
"position-area",

0 commit comments

Comments
 (0)