mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 14:27:35 +00:00
LibWeb: Implement CSS transforms on stacking contexts
Since there is currently no easy way to handle rotations and skews with LibGfx this only implements translation and scaling by first constructing a general 4x4 transformation matrix like outlined in the css-transforms-1 specification. This is then downgraded to a Gfx::AffineTransform in order to transform the destination rectangle used with draw_scaled_bitmap() While rotation would be nice this already looks pretty good :^)
This commit is contained in:
parent
7c79fc209f
commit
a2331e8dd3
4 changed files with 121 additions and 42 deletions
|
@ -73,44 +73,6 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box()
|
||||||
// We can also layout absolutely positioned boxes within this BFC.
|
// We can also layout absolutely positioned boxes within this BFC.
|
||||||
for (auto& box : m_absolutely_positioned_boxes)
|
for (auto& box : m_absolutely_positioned_boxes)
|
||||||
layout_absolutely_positioned_element(box);
|
layout_absolutely_positioned_element(box);
|
||||||
|
|
||||||
// FIXME: Transforms should be a painting concept, not a layout concept.
|
|
||||||
apply_transformations_to_children(root());
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlockFormattingContext::apply_transformations_to_children(Box const& box)
|
|
||||||
{
|
|
||||||
box.for_each_child_of_type<Box>([&](auto& child_box) {
|
|
||||||
float transform_y_offset = 0.0f;
|
|
||||||
if (!child_box.computed_values().transformations().is_empty()) {
|
|
||||||
// FIXME: All transformations can be interpreted as successive 3D-matrix operations on the box, we don't do that yet.
|
|
||||||
// https://drafts.csswg.org/css-transforms/#serialization-of-the-computed-value
|
|
||||||
for (auto transformation : child_box.computed_values().transformations()) {
|
|
||||||
switch (transformation.function) {
|
|
||||||
case CSS::TransformFunction::TranslateY:
|
|
||||||
if (transformation.values.size() != 1)
|
|
||||||
continue;
|
|
||||||
transformation.values.first().visit(
|
|
||||||
[&](CSS::Length& value) {
|
|
||||||
transform_y_offset += value.to_px(child_box);
|
|
||||||
},
|
|
||||||
[&](float value) {
|
|
||||||
transform_y_offset += value;
|
|
||||||
},
|
|
||||||
[&](auto&) {
|
|
||||||
dbgln("FIXME: Implement unsupported transformation function value type!");
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
dbgln("FIXME: Implement missing transform function!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& child_box_state = m_state.get_mutable(child_box);
|
|
||||||
auto untransformed_offset = child_box_state.offset;
|
|
||||||
child_box_state.offset = Gfx::FloatPoint { untransformed_offset.x(), untransformed_offset.y() + transform_y_offset };
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mode)
|
void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mode)
|
||||||
|
|
|
@ -60,8 +60,6 @@ private:
|
||||||
|
|
||||||
void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode);
|
void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode);
|
||||||
|
|
||||||
void apply_transformations_to_children(Box const&);
|
|
||||||
|
|
||||||
void layout_list_item_marker(ListItemBox const&);
|
void layout_list_item_marker(ListItemBox const&);
|
||||||
|
|
||||||
enum class FloatSide {
|
enum class FloatSide {
|
||||||
|
|
|
@ -4,9 +4,13 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Debug.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <LibGfx/AffineTransform.h>
|
||||||
|
#include <LibGfx/Matrix4x4.h>
|
||||||
#include <LibGfx/Painter.h>
|
#include <LibGfx/Painter.h>
|
||||||
|
#include <LibGfx/Rect.h>
|
||||||
#include <LibWeb/Layout/Box.h>
|
#include <LibWeb/Layout/Box.h>
|
||||||
#include <LibWeb/Layout/InitialContainingBlock.h>
|
#include <LibWeb/Layout/InitialContainingBlock.h>
|
||||||
#include <LibWeb/Layout/ReplacedBox.h>
|
#include <LibWeb/Layout/ReplacedBox.h>
|
||||||
|
@ -137,6 +141,104 @@ void StackingContext::paint_internal(PaintContext& context) const
|
||||||
paint_descendants(context, m_box, StackingContextPaintPhase::FocusAndOverlay);
|
paint_descendants(context, m_box, StackingContextPaintPhase::FocusAndOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gfx::FloatMatrix4x4 StackingContext::get_transformation_matrix(CSS::Transformation const& transformation) const
|
||||||
|
{
|
||||||
|
Vector<float> float_values;
|
||||||
|
for (auto const& value : transformation.values) {
|
||||||
|
value.visit(
|
||||||
|
[&](CSS::Length const& value) {
|
||||||
|
float_values.append(value.to_px(m_box));
|
||||||
|
},
|
||||||
|
[&](float value) {
|
||||||
|
float_values.append(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (transformation.function) {
|
||||||
|
case CSS::TransformFunction::Matrix:
|
||||||
|
if (float_values.size() == 6)
|
||||||
|
return Gfx::FloatMatrix4x4(float_values[0], float_values[2], 0, float_values[4],
|
||||||
|
float_values[1], float_values[3], 0, float_values[5],
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::Translate:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(1, 0, 0, float_values[0],
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
if (float_values.size() == 2)
|
||||||
|
return Gfx::FloatMatrix4x4(1, 0, 0, float_values[0],
|
||||||
|
0, 1, 0, float_values[1],
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::TranslateX:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(1, 0, 0, float_values[0],
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::TranslateY:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
|
||||||
|
0, 1, 0, float_values[0],
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::Scale:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(float_values[0], 0, 0, 0,
|
||||||
|
0, float_values[0], 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
if (float_values.size() == 2)
|
||||||
|
return Gfx::FloatMatrix4x4(float_values[0], 0, 0, 0,
|
||||||
|
0, float_values[1], 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::ScaleX:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(float_values[0], 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
case CSS::TransformFunction::ScaleY:
|
||||||
|
if (float_values.size() == 1)
|
||||||
|
return Gfx::FloatMatrix4x4(1, 0, 0, 0,
|
||||||
|
0, float_values[0], 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Unhandled transformation function {}", CSS::TransformationStyleValue::create(transformation.function, {})->to_string());
|
||||||
|
}
|
||||||
|
return Gfx::FloatMatrix4x4::identity();
|
||||||
|
}
|
||||||
|
|
||||||
|
Gfx::FloatMatrix4x4 StackingContext::combine_transformations(Vector<CSS::Transformation> const& transformations) const
|
||||||
|
{
|
||||||
|
auto matrix = Gfx::FloatMatrix4x4::identity();
|
||||||
|
|
||||||
|
for (auto const& transform : transformations)
|
||||||
|
matrix = matrix * get_transformation_matrix(transform);
|
||||||
|
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: This extracts the affine 2D part of the full transformation matrix.
|
||||||
|
// Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
|
||||||
|
Gfx::AffineTransform StackingContext::combine_transformations_2d(Vector<CSS::Transformation> const& transformations) const
|
||||||
|
{
|
||||||
|
auto matrix = combine_transformations(transformations);
|
||||||
|
auto* m = matrix.elements();
|
||||||
|
return Gfx::AffineTransform(m[0][0], m[1][0], m[0][1], m[1][1], m[0][3], m[1][3]);
|
||||||
|
}
|
||||||
|
|
||||||
void StackingContext::paint(PaintContext& context) const
|
void StackingContext::paint(PaintContext& context) const
|
||||||
{
|
{
|
||||||
Gfx::PainterStateSaver saver(context.painter());
|
Gfx::PainterStateSaver saver(context.painter());
|
||||||
|
@ -148,7 +250,9 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
if (opacity == 0.0f)
|
if (opacity == 0.0f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (opacity < 1.0f) {
|
auto affine_transform = combine_transformations_2d(m_box.computed_values().transformations());
|
||||||
|
|
||||||
|
if (opacity < 1.0f || !affine_transform.is_identity()) {
|
||||||
auto bitmap_or_error = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, context.painter().target()->size());
|
auto bitmap_or_error = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, context.painter().target()->size());
|
||||||
if (bitmap_or_error.is_error())
|
if (bitmap_or_error.is_error())
|
||||||
return;
|
return;
|
||||||
|
@ -156,7 +260,13 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
Gfx::Painter painter(bitmap);
|
Gfx::Painter painter(bitmap);
|
||||||
PaintContext paint_context(painter, context.palette(), context.scroll_offset());
|
PaintContext paint_context(painter, context.palette(), context.scroll_offset());
|
||||||
paint_internal(paint_context);
|
paint_internal(paint_context);
|
||||||
context.painter().blit(Gfx::IntPoint(m_box.paint_box()->absolute_position()), bitmap, Gfx::IntRect(m_box.paint_box()->absolute_rect()), opacity);
|
|
||||||
|
// FIXME: Use the transform origin specified in CSS or SVG
|
||||||
|
auto transform_origin = m_box.paint_box()->absolute_position();
|
||||||
|
auto source_rect = m_box.paint_box()->absolute_rect().translated(-transform_origin);
|
||||||
|
auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform_origin);
|
||||||
|
source_rect.translate_by(transform_origin);
|
||||||
|
context.painter().draw_scaled_bitmap(Gfx::rounded_int_rect(transformed_destination_rect), *bitmap, source_rect, opacity, Gfx::Painter::ScalingMode::BilinearBlend);
|
||||||
} else {
|
} else {
|
||||||
paint_internal(context);
|
paint_internal(context);
|
||||||
}
|
}
|
||||||
|
@ -252,6 +362,11 @@ void StackingContext::dump(int indent) const
|
||||||
else
|
else
|
||||||
builder.append("auto");
|
builder.append("auto");
|
||||||
builder.append(')');
|
builder.append(')');
|
||||||
|
|
||||||
|
auto affine_transform = combine_transformations_2d(m_box.computed_values().transformations());
|
||||||
|
if (!affine_transform.is_identity()) {
|
||||||
|
builder.appendff(", transform: {}", affine_transform);
|
||||||
|
}
|
||||||
dbgln("{}", builder.string_view());
|
dbgln("{}", builder.string_view());
|
||||||
for (auto& child : m_children)
|
for (auto& child : m_children)
|
||||||
child->dump(indent + 1);
|
child->dump(indent + 1);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibGfx/Matrix4x4.h>
|
||||||
#include <LibWeb/Layout/Node.h>
|
#include <LibWeb/Layout/Node.h>
|
||||||
#include <LibWeb/Painting/Paintable.h>
|
#include <LibWeb/Painting/Paintable.h>
|
||||||
|
|
||||||
|
@ -41,6 +42,9 @@ private:
|
||||||
Vector<StackingContext*> m_children;
|
Vector<StackingContext*> m_children;
|
||||||
|
|
||||||
void paint_internal(PaintContext&) const;
|
void paint_internal(PaintContext&) const;
|
||||||
|
Gfx::FloatMatrix4x4 get_transformation_matrix(CSS::Transformation const& transformation) const;
|
||||||
|
Gfx::FloatMatrix4x4 combine_transformations(Vector<CSS::Transformation> const& transformations) const;
|
||||||
|
Gfx::AffineTransform combine_transformations_2d(Vector<CSS::Transformation> const& transformations) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue