Skip to content

Commit

Permalink
LibWeb: Layout the fieldset's rendered legend
Browse files Browse the repository at this point in the history
  • Loading branch information
kostyafarber committed Nov 24, 2024
1 parent 01f4bbb commit b190b04
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 15 deletions.
13 changes: 7 additions & 6 deletions Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ set(SOURCES
Painting/DisplayList.cpp
Painting/DisplayListPlayerSkia.cpp
Painting/DisplayListRecorder.cpp
Painting/FieldSetPaintable.cpp
Painting/GradientPainting.cpp
Painting/ImagePaintable.cpp
Painting/LabelablePaintable.cpp
Expand Down Expand Up @@ -826,12 +827,12 @@ compile_ipc(Worker/WebWorkerClient.ipc Worker/WebWorkerClientEndpoint.h)
compile_ipc(Worker/WebWorkerServer.ipc Worker/WebWorkerServerEndpoint.h)

invoke_generator(
"AriaRoles.cpp"
Lagom::GenerateAriaRoles
"${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
"ARIA/AriaRoles.h"
"ARIA/AriaRoles.cpp"
arguments -j "${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
"AriaRoles.cpp"
Lagom::GenerateAriaRoles
"${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
"ARIA/AriaRoles.h"
"ARIA/AriaRoles.cpp"
arguments -j "${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
)

generate_css_implementation()
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibWeb/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ namespace Web::Painting {
class AudioPaintable;
class ButtonPaintable;
class CheckBoxPaintable;
class FieldSetPaintable;
class LabelablePaintable;
class MediaPaintable;
class Paintable;
Expand Down
34 changes: 34 additions & 0 deletions Libraries/LibWeb/Layout/BlockFormattingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/BlockFormattingContext.h>
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/FieldSetBox.h>
#include <LibWeb/Layout/InlineFormattingContext.h>
#include <LibWeb/Layout/LegendBox.h>
#include <LibWeb/Layout/LineBuilder.h>
#include <LibWeb/Layout/ListItemBox.h>
#include <LibWeb/Layout/ListItemMarkerBox.h>
Expand Down Expand Up @@ -73,6 +75,38 @@ void BlockFormattingContext::run(AvailableSpace const& available_space)
return;
}

if (is<FieldSetBox>(root())) {
if (root().children_are_inline())
layout_inline_children(root(), available_space);
else
layout_block_level_children(root(), available_space);

auto const& fieldset_box = verify_cast<FieldSetBox>(root());
if (!(fieldset_box.has_rendered_legend())) {
return;
}

auto const* legend = root().first_child_of_type<LegendBox>();
auto& legend_state = m_state.get_mutable(*legend);
auto& fieldset_state = m_state.get_mutable(root());

// The element is expected to be positioned in the block-flow direction such that
// its border box is centered over the border on the block-start side of the fieldset element.
// FIXME: this should take writing modes into consideration.
auto legend_height = legend_state.border_box_height();
auto new_y = -((legend_height) / 2) - fieldset_state.padding_top;
legend_state.set_content_offset({ legend_state.offset.x(), new_y });

// If the computed value of 'inline-size' is 'auto',
// then the used value is the fit-content inline size.
if (legend->computed_values().width().is_auto()) {
auto width = calculate_fit_content_width(*legend, available_space);
legend_state.set_content_width(width);
}

return;
}

if (root().children_are_inline())
layout_inline_children(root(), available_space);
else
Expand Down
28 changes: 22 additions & 6 deletions Libraries/LibWeb/Layout/FieldSetBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/HTMLLegendElement.h>
#include <LibWeb/Layout/FieldSetBox.h>
#include <LibWeb/Layout/LegendBox.h>
#include <LibWeb/Painting/FieldSetPaintable.h>

namespace Web::Layout {

Expand All @@ -22,13 +24,27 @@ FieldSetBox::~FieldSetBox() = default;
bool FieldSetBox::has_rendered_legend() const
{
// https://html.spec.whatwg.org/#rendered-legend
if (this->has_children() && this->first_child()->is_legend_box()) {
auto* first_child = this->first_child();
return first_child->computed_values().float_() == CSS::Float::None
&& first_child->computed_values().position() != CSS::Positioning::Absolute
&& first_child->computed_values().position() != CSS::Positioning::Fixed;
bool has_rendered_legend = false;
if (has_children()) {
for_each_child_of_type<Box>([&](Box const& child) {
if (child.is_anonymous())
return IterationDecision::Continue;

if (!child.is_legend_box())
return IterationDecision::Break;

has_rendered_legend = child.computed_values().float_() == CSS::Float::None
&& child.computed_values().position() != CSS::Positioning::Absolute
&& child.computed_values().position() != CSS::Positioning::Fixed;
return IterationDecision::Break;
});
}
return false;
return has_rendered_legend;
}

GC::Ptr<Painting::Paintable> FieldSetBox::create_paintable() const
{
return Painting::FieldSetPaintable::create(*this);
}

}
6 changes: 3 additions & 3 deletions Libraries/LibWeb/Layout/FieldSetBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#include <LibWeb/Forward.h>
#include <LibWeb/Layout/BlockContainer.h>

#include <LibWeb/Painting/FieldSetPaintable.h>
namespace Web::Layout {

class FieldSetBox final : public BlockContainer {
Expand All @@ -22,10 +22,10 @@ class FieldSetBox final : public BlockContainer {
DOM::Element& dom_node() { return static_cast<DOM::Element&>(*BlockContainer::dom_node()); }
DOM::Element const& dom_node() const { return static_cast<DOM::Element const&>(*BlockContainer::dom_node()); }

void layout_legend() const;
bool has_rendered_legend() const;
virtual GC::Ptr<Painting::Paintable> create_paintable() const override;

private:
bool has_rendered_legend() const;
virtual bool is_fieldset_box() const final
{
return true;
Expand Down
106 changes: 106 additions & 0 deletions Libraries/LibWeb/Painting/FieldSetPaintable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2024, Kostya Farber <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibWeb/Layout/LegendBox.h>
#include <LibWeb/Painting/FieldSetPaintable.h>

namespace Web::Painting {
GC_DEFINE_ALLOCATOR(FieldSetPaintable);

GC::Ref<FieldSetPaintable> FieldSetPaintable::create(Layout::FieldSetBox const& layout_box)
{
return layout_box.heap().allocate<FieldSetPaintable>(layout_box);
}

FieldSetPaintable::FieldSetPaintable(Layout::FieldSetBox const& layout_box)
: PaintableBox(layout_box)
{
}

Layout::FieldSetBox& FieldSetPaintable::layout_box()
{
return static_cast<Layout::FieldSetBox&>(layout_node());
}

Layout::FieldSetBox const& FieldSetPaintable::layout_box() const
{
return static_cast<Layout::FieldSetBox const&>(layout_node());
}

void FieldSetPaintable::paint(PaintContext& context, PaintPhase phase) const
{
if (!is_visible())
return;

if (phase != PaintPhase::Border) {
PaintableBox::paint(context, phase);
return;
}

if (!(layout_box().has_rendered_legend())) {
PaintableBox::paint(context, phase);
return;
}

auto& display_list_recorder = context.display_list_recorder();

auto const* legend_box = layout_box().first_child_of_type<Layout::LegendBox>();
auto const* const legend_paintable = legend_box->paintable_box();

auto legend_border_rect = context.rounded_device_rect(legend_paintable->absolute_border_box_rect());
auto fieldset_border_rect = context.rounded_device_rect(absolute_border_box_rect());

BordersData borders_data = BordersData {
.top = CSS::BorderData(),
.right = box_model().border.right == 0 ? CSS::BorderData() : computed_values().border_right(),
.bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
.left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
};

paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));

auto top_border_data = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top();
auto top_border = context.enclosing_device_pixels(top_border_data.width).value();

// if fieldset has a rendered legend, the top border is not
// expected to be painted behind the border box of the legend
DevicePixelRect left_segment = {
fieldset_border_rect.x(),
fieldset_border_rect.y(),
legend_border_rect.x() - fieldset_border_rect.x(),
top_border
};

DevicePixelRect right_segment = {
legend_border_rect.right(),
fieldset_border_rect.y(),
fieldset_border_rect.right() - legend_border_rect.right(),
top_border
};

BordersData top_border_only = BordersData {
.top = top_border_data,
.right = CSS::BorderData(),
.bottom = CSS::BorderData(),
.left = CSS::BorderData(),
};

display_list_recorder.save();
display_list_recorder.add_clip_rect(left_segment.to_type<int>());
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), top_border_only.to_device_pixels(context));
display_list_recorder.restore();

display_list_recorder.save();
display_list_recorder.add_clip_rect(right_segment.to_type<int>());
paint_all_borders(
display_list_recorder,
fieldset_border_rect,
normalized_border_radii_data().as_corners(context),
top_border_only.to_device_pixels(context));
display_list_recorder.restore();
}

}
32 changes: 32 additions & 0 deletions Libraries/LibWeb/Painting/FieldSetPaintable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024, Kostya Farber <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <LibWeb/Forward.h>
#include <LibWeb/Layout/FieldSetBox.h>
#include <LibWeb/Painting/PaintContext.h>
#include <LibWeb/Painting/PaintableBox.h>

namespace Web::Painting {

class FieldSetPaintable final : public PaintableBox {
GC_CELL(FieldSetPaintable, PaintableBox);
GC_DECLARE_ALLOCATOR(FieldSetPaintable);

public:
static GC::Ref<FieldSetPaintable> create(Layout::FieldSetBox const&);

virtual void paint(PaintContext&, PaintPhase) const override;

private:
Layout::FieldSetBox& layout_box();
Layout::FieldSetBox const& layout_box() const;

explicit FieldSetPaintable(Layout::FieldSetBox const&);
};

}
18 changes: 18 additions & 0 deletions Tests/LibWeb/Layout/expected/fieldset-with-rendered-legend.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x36.59375 children: not-inline
FieldSetBox <fieldset> at (24,15.59375) content-size 752x17 [BFC] children: not-inline
LegendBox <legend> at (26,1.5) content-size 36.328125x17 children: inline
frag 0 from TextNode start: 0, length: 5, rect: [26,1.5 36.328125x17] baseline: 13.296875
"login"
TextNode <#text>
BlockContainer <(anonymous)> at (8,44.59375) content-size 784x0 children: inline
TextNode <#text>

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x36.59375] overflow: [8,1.5 784x51.09375]
FieldSetPaintable (FieldSetBox<FIELDSET>) [10,8 780x36.59375] overflow: [12,1.5 776x51.09375]
PaintableWithLines (LegendBox<LEGEND>) [24,1.5 40.328125x17]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,44.59375 784x0]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<fieldset><legend>login</legend></fieldset>

0 comments on commit b190b04

Please sign in to comment.