1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-30 19:38:12 +00:00
serenity/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp
Aliaksandr Kalenik 6c645f3a9f LibWeb: Paint fragments contained by inline node as part of this node
Fragments contained by the inline node should be painted in the
foreground phase for this node, instead of being painted as a part of
the containing PaintableWithLines. This change implements that by
marking all fragments contained by inline nodes so they can be skipped
while painting the content of PaintableWithLines. This is an ugly way,
and instead, we should make InlinePaintables own all fragments
contained by them.
2024-01-05 19:36:55 +01:00

220 lines
11 KiB
C++

/*
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/AntiAliasingPainter.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/ImageBox.h>
#include <LibWeb/Painting/BackgroundPainting.h>
#include <LibWeb/Painting/InlinePaintable.h>
#include <LibWeb/Painting/ShadowPainting.h>
namespace Web::Painting {
JS::NonnullGCPtr<InlinePaintable> InlinePaintable::create(Layout::InlineNode const& layout_node)
{
return layout_node.heap().allocate_without_realm<InlinePaintable>(layout_node);
}
InlinePaintable::InlinePaintable(Layout::InlineNode const& layout_node)
: Paintable(layout_node)
{
}
Layout::InlineNode const& InlinePaintable::layout_node() const
{
return static_cast<Layout::InlineNode const&>(Paintable::layout_node());
}
void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const
{
auto& painter = context.recording_painter();
if (phase == PaintPhase::Background) {
auto containing_block_position_in_absolute_coordinates = containing_block()->paintable_box()->absolute_position();
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
CSSPixelRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
if (is_first_fragment) {
auto extra_start_width = box_model().padding.left;
absolute_fragment_rect.translate_by(-extra_start_width, 0);
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
}
if (is_last_fragment) {
auto extra_end_width = box_model().padding.right;
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
}
auto const& border_radii_data = fragment.border_radii_data();
paint_background(context, layout_node(), absolute_fragment_rect, computed_values().background_color(), computed_values().image_rendering(), &computed_values().background_layers(), border_radii_data);
if (auto computed_box_shadow = computed_values().box_shadow(); !computed_box_shadow.is_empty()) {
Vector<ShadowData> resolved_box_shadow_data;
resolved_box_shadow_data.ensure_capacity(computed_box_shadow.size());
for (auto const& layer : computed_box_shadow) {
resolved_box_shadow_data.empend(
layer.color,
layer.offset_x.to_px(layout_node()),
layer.offset_y.to_px(layout_node()),
layer.blur_radius.to_px(layout_node()),
layer.spread_distance.to_px(layout_node()),
layer.placement == CSS::ShadowPlacement::Outer ? ShadowPlacement::Outer : ShadowPlacement::Inner);
}
auto borders_data = BordersData {
.top = computed_values().border_top(),
.right = computed_values().border_right(),
.bottom = computed_values().border_bottom(),
.left = computed_values().border_left(),
};
auto absolute_fragment_rect_bordered = absolute_fragment_rect.inflated(
borders_data.top.width, borders_data.right.width,
borders_data.bottom.width, borders_data.left.width);
paint_box_shadow(context, absolute_fragment_rect_bordered, absolute_fragment_rect,
borders_data, border_radii_data, resolved_box_shadow_data);
}
return IterationDecision::Continue;
});
}
auto paint_border_or_outline = [&](Optional<BordersData> outline_data = {}, CSSPixels outline_offset = 0) {
auto borders_data = BordersData {
.top = computed_values().border_top(),
.right = computed_values().border_right(),
.bottom = computed_values().border_bottom(),
.left = computed_values().border_left(),
};
auto containing_block_position_in_absolute_coordinates = containing_block()->paintable_box()->absolute_position();
for_each_fragment([&](auto const& fragment, bool is_first_fragment, bool is_last_fragment) {
CSSPixelRect absolute_fragment_rect { containing_block_position_in_absolute_coordinates.translated(fragment.offset()), fragment.size() };
if (is_first_fragment) {
auto extra_start_width = box_model().padding.left;
absolute_fragment_rect.translate_by(-extra_start_width, 0);
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
}
if (is_last_fragment) {
auto extra_end_width = box_model().padding.right;
absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
}
auto borders_rect = absolute_fragment_rect.inflated(borders_data.top.width, borders_data.right.width, borders_data.bottom.width, borders_data.left.width);
auto border_radii_data = fragment.border_radii_data();
if (outline_data.has_value()) {
auto outline_offset_x = outline_offset;
auto outline_offset_y = outline_offset;
// "Both the height and the width of the outside of the shape drawn by the outline should not
// become smaller than twice the computed value of the outline-width property to make sure
// that an outline can be rendered even with large negative values."
// https://www.w3.org/TR/css-ui-4/#outline-offset
// So, if the horizontal outline offset is > half the borders_rect's width then we set it to that.
// (And the same for y)
if ((borders_rect.width() / 2) + outline_offset_x < 0)
outline_offset_x = -borders_rect.width() / 2;
if ((borders_rect.height() / 2) + outline_offset_y < 0)
outline_offset_y = -borders_rect.height() / 2;
border_radii_data.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
borders_rect.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
context.recording_painter().paint_borders(context.rounded_device_rect(borders_rect), border_radii_data.as_corners(context), outline_data->to_device_pixels(context));
} else {
context.recording_painter().paint_borders(context.rounded_device_rect(borders_rect), border_radii_data.as_corners(context), borders_data.to_device_pixels(context));
}
return IterationDecision::Continue;
});
};
if (phase == PaintPhase::Border) {
paint_border_or_outline();
}
if (phase == PaintPhase::Outline) {
auto outline_width = computed_values().outline_width().to_px(layout_node());
auto maybe_outline_data = borders_data_for_outline(layout_node(), computed_values().outline_color(), computed_values().outline_style(), outline_width);
if (maybe_outline_data.has_value()) {
paint_border_or_outline(maybe_outline_data.value(), computed_values().outline_offset().to_px(layout_node()));
}
}
if (phase == PaintPhase::Foreground) {
for_each_fragment([&](auto const& fragment, bool, bool) {
if (is<Layout::TextNode>(fragment.layout_node()))
paint_text_fragment(context, static_cast<Layout::TextNode const&>(fragment.layout_node()), fragment, phase);
});
}
if (phase == PaintPhase::Overlay && layout_node().document().inspected_layout_node() == &layout_node()) {
// FIXME: This paints a double-thick border between adjacent fragments, where ideally there
// would be none. Once we implement non-rectangular outlines for the `outline` CSS
// property, we can use that here instead.
for_each_fragment([&](auto const& fragment, bool, bool) {
painter.draw_rect(context.enclosing_device_rect(fragment.absolute_rect()).template to_type<int>(), Color::Magenta);
return IterationDecision::Continue;
});
}
}
template<typename Callback>
void InlinePaintable::for_each_fragment(Callback callback) const
{
// FIXME: This will be slow if the containing block has a lot of fragments!
Vector<Layout::LineBoxFragment const&> fragments;
verify_cast<PaintableWithLines>(*containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) {
if (layout_node().is_inclusive_ancestor_of(fragment.layout_node()))
fragments.append(fragment);
return IterationDecision::Continue;
});
for (size_t i = 0; i < fragments.size(); ++i) {
auto const& fragment = fragments[i];
callback(fragment, i == 0, i == fragments.size() - 1);
}
}
void InlinePaintable::mark_contained_fragments()
{
verify_cast<PaintableWithLines>(*containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) {
if (layout_node().is_inclusive_ancestor_of(fragment.layout_node()))
const_cast<Layout::LineBoxFragment&>(fragment).set_contained_by_inline_node();
return IterationDecision::Continue;
});
}
CSSPixelRect InlinePaintable::bounding_rect() const
{
auto top = CSSPixels::max();
auto left = CSSPixels::max();
auto right = CSSPixels::min();
auto bottom = CSSPixels::min();
auto has_fragments = false;
for_each_fragment([&](auto const& fragment, bool, bool) {
has_fragments = true;
auto fragment_absolute_rect = fragment.absolute_rect();
if (fragment_absolute_rect.top() < top)
top = fragment_absolute_rect.top();
if (fragment_absolute_rect.left() < left)
left = fragment_absolute_rect.left();
if (fragment_absolute_rect.right() > right)
right = fragment_absolute_rect.right();
if (fragment_absolute_rect.bottom() > bottom)
bottom = fragment_absolute_rect.bottom();
});
if (!has_fragments) {
// FIXME: This is adhoc, and we should return rect of empty fragment instead.
auto containing_block_position_in_absolute_coordinates = containing_block()->paintable_box()->absolute_position();
return { containing_block_position_in_absolute_coordinates, { 0, 0 } };
}
return { left, top, right - left, bottom - top };
}
}