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.
This commit is contained in:
Sam Atkins 2024-01-26 15:56:25 +00:00 committed by Andreas Kling
parent 391cfdc085
commit e025bcc4f9
4 changed files with 117 additions and 2 deletions

View file

@ -0,0 +1,36 @@
<!doctype html>
<link rel="match" href="reference/css-transform-box-ref.html" />
<style>
svg {
width: 400px;
height: 400px;
border: 1px solid #d9d9d9;
}
.box {
stroke-width: 4;
transform-origin: 20% 20%;
transform: scale(1.5);
}
div.box {
width: 40px;
height: 40px;
border: solid black;
border-width: 10px 20px 30px 40px;
margin: 80px;
}
</style>
<div class="box" style="transform-box: content-box">Hi</div>
<div class="box" style="transform-box: border-box">Hi</div>
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<!-- NOTE: We don't currently calculate the fill or stroke boxes for SVG elements, so the test will need to be
rebaselined once we do! -->
<rect class="box" style="transform-box: fill-box" x="10" y="10" width="10" height="10" stroke="black" fill="yellow" />
<circle fill="red" r="1" cx="15" cy="15" />
<rect class="box" style="transform-box: stroke-box" x="10" y="40" width="10" height="10" stroke="black" fill="cyan" />
<circle fill="red" r="1" cx="15" cy="45" />
<rect class="box" style="transform-box: view-box" x="10" y="70" width="10" height="10" stroke="black" fill="green" />
<circle fill="red" r="1" cx="15" cy="75" />
</svg>

View file

@ -0,0 +1,15 @@
<style>
* {
margin: 0;
}
body {
background-color: white;
}
</style>
<!-- To rebase:
1. Open css-backgrounds.html in Ladybird
2. Resize the window just above the width of the largest element
3. Right click > "Take Full Screenshot"
4. Update the image below:
-->
<img src="./images/css-transform-box-ref.png">

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2024, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -12,6 +13,7 @@
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/InlinePaintable.h>
#include <LibWeb/Painting/SVGPathPaintable.h>
#include <LibWeb/Painting/SVGSVGPaintable.h>
#include <LibWeb/Painting/TextPaintable.h>
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 <svg> one. Is that correct?
// And is it correct to use `else` below?
if (is<Painting::SVGPaintable>(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<Painting::SVGSVGPaintable>();
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 });