From e025bcc4f95e37cc8019bcbcb8557d2893a79953 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 26 Jan 2024 15:56:25 +0000 Subject: [PATCH] LibWeb: Make use of transform-box when calculating transforms We don't currently calculate the fill- or stroke-boxes of SVG elements, so for now we use the content- and border-boxes respectively, as those are the closest equivalents. The test will need updating when we do support them. Also, the test is a screenshot because of rendering differences when applying transforms: a 20px box does not get painted the same as a 10px box scaled up 2x. Otherwise that would be the more ideal form of test. --- Tests/LibWeb/Ref/css-transform-box.html | 36 ++++++++++ .../Ref/reference/css-transform-box-ref.html | 15 ++++ .../images/css-transform-box-ref.png | Bin 0 -> 8362 bytes .../Libraries/LibWeb/Layout/LayoutState.cpp | 68 +++++++++++++++++- 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 Tests/LibWeb/Ref/css-transform-box.html create mode 100644 Tests/LibWeb/Ref/reference/css-transform-box-ref.html create mode 100644 Tests/LibWeb/Ref/reference/images/css-transform-box-ref.png diff --git a/Tests/LibWeb/Ref/css-transform-box.html b/Tests/LibWeb/Ref/css-transform-box.html new file mode 100644 index 0000000000..9ca3dbcf38 --- /dev/null +++ b/Tests/LibWeb/Ref/css-transform-box.html @@ -0,0 +1,36 @@ + + + +
Hi
+
Hi
+ + + + + + + + + + + diff --git a/Tests/LibWeb/Ref/reference/css-transform-box-ref.html b/Tests/LibWeb/Ref/reference/css-transform-box-ref.html new file mode 100644 index 0000000000..57ae32963b --- /dev/null +++ b/Tests/LibWeb/Ref/reference/css-transform-box-ref.html @@ -0,0 +1,15 @@ + + + diff --git a/Tests/LibWeb/Ref/reference/images/css-transform-box-ref.png b/Tests/LibWeb/Ref/reference/images/css-transform-box-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..58fe5260b51e5c5ecec0ee6031ee94955134dae9 GIT binary patch literal 8362 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 literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index c2b19b2c34..886524f794 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022-2023, Andreas Kling + * Copyright (c) 2024, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,7 @@ #include #include #include +#include #include namespace Web::Layout { @@ -369,8 +371,70 @@ void LayoutState::resolve_layout_dependent_properties() } auto const& transform_origin = paintable_box.computed_values().transform_origin(); - // FIXME: respect transform-box property - auto const& reference_box = paintable_box.absolute_border_box_rect(); + // https://www.w3.org/TR/css-transforms-1/#transform-box + auto transform_box = paintable_box.computed_values().transform_box(); + // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for + // border-box is stroke-box. + // FIXME: This currently detects any SVG element except the one. Is that correct? + // And is it correct to use `else` below? + if (is(paintable_box)) { + switch (transform_box) { + case CSS::TransformBox::ContentBox: + transform_box = CSS::TransformBox::FillBox; + break; + case CSS::TransformBox::BorderBox: + transform_box = CSS::TransformBox::StrokeBox; + break; + default: + break; + } + } + // For elements with associated CSS layout box, the used value for fill-box is content-box and for + // stroke-box and view-box is border-box. + else { + switch (transform_box) { + case CSS::TransformBox::FillBox: + transform_box = CSS::TransformBox::ContentBox; + break; + case CSS::TransformBox::StrokeBox: + case CSS::TransformBox::ViewBox: + transform_box = CSS::TransformBox::BorderBox; + break; + default: + break; + } + } + + CSSPixelRect reference_box = [&]() { + switch (transform_box) { + case CSS::TransformBox::ContentBox: + // Uses the content box as reference box. + // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. + return paintable_box.absolute_rect(); + case CSS::TransformBox::BorderBox: + // Uses the border box as reference box. + // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box. + return paintable_box.absolute_border_box_rect(); + case CSS::TransformBox::FillBox: + // Uses the object bounding box as reference box. + // FIXME: For now we're using the content rect as an approximation. + return paintable_box.absolute_rect(); + case CSS::TransformBox::StrokeBox: + // Uses the stroke bounding box as reference box. + // FIXME: For now we're using the border rect as an approximation. + return paintable_box.absolute_border_box_rect(); + case CSS::TransformBox::ViewBox: + // Uses the nearest SVG viewport as reference box. + // FIXME: If a viewBox attribute is specified for the SVG viewport creating element: + // - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute. + // - The dimension of the reference box is set to the width and height values of the viewBox attribute. + auto* svg_paintable = paintable_box.first_ancestor_of_type(); + if (!svg_paintable) + return paintable_box.absolute_border_box_rect(); + return svg_paintable->absolute_rect(); + } + VERIFY_NOT_REACHED(); + }(); auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width()); auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height()); paintable_box.set_transform_origin({ x, y });