LibWeb: Store "text for rendering" in TextPaintable

Instead of TextPaintable fragments being an offset+length view into the
layout node, they are now a view into the paintable instead.

This removes an awkward time window where we'd have bogus state in text
fragments after layout invalidation but before relayout. It also makes
the code slightly nicer in general, since there's less mixing of layout
and painting concepts.
This commit is contained in:
Andreas Kling 2024-03-18 10:25:57 +01:00
parent 2be47c3d7a
commit dd8504c68d
Notes: sideshowbarker 2024-07-17 01:13:25 +09:00
9 changed files with 54 additions and 43 deletions

View File

@ -466,7 +466,7 @@ Optional<TextNode::Chunk> TextNode::ChunkIterator::try_commit_chunk(Utf8View::It
JS::GCPtr<Painting::Paintable> TextNode::create_paintable() const
{
return Painting::TextPaintable::create(*this);
return Painting::TextPaintable::create(*this, text_for_rendering());
}
}

View File

@ -21,6 +21,7 @@
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/TextPaintable.h>
#include <LibWeb/UIEvents/EventNames.h>
#include <LibWeb/UIEvents/KeyboardEvent.h>
#include <LibWeb/UIEvents/MouseEvent.h>
@ -611,16 +612,15 @@ bool EventHandler::handle_doubleclick(CSSPixelPoint position, CSSPixelPoint scre
if (button == GUI::MouseButton::Primary) {
if (auto result = paint_root()->hit_test(position, Painting::HitTestType::TextCursor); result.has_value()) {
auto hit_paintable = result->paintable;
if (!hit_paintable->dom_node())
if (!result->paintable->dom_node())
return true;
auto const& hit_layout_node = hit_paintable->layout_node();
if (!hit_layout_node.is_text_node())
if (!is<Painting::TextPaintable>(*result->paintable))
return true;
auto& hit_paintable = static_cast<Painting::TextPaintable const&>(*result->paintable);
auto& hit_dom_node = verify_cast<DOM::Text>(*hit_paintable->dom_node());
auto const& text_for_rendering = verify_cast<Layout::TextNode>(hit_layout_node).text_for_rendering();
auto& hit_dom_node = const_cast<DOM::Text&>(verify_cast<DOM::Text>(*hit_paintable.dom_node()));
auto const& text_for_rendering = hit_paintable.text_for_rendering();
int first_word_break_before = [&] {
// Start from one before the index position to prevent selecting only spaces between words, caused by the addition below.

View File

@ -9,6 +9,7 @@
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Painting/BackgroundPainting.h>
#include <LibWeb/Painting/InlinePaintable.h>
#include <LibWeb/Painting/TextPaintable.h>
namespace Web::Painting {
@ -174,8 +175,8 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const
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 (is<TextPaintable>(fragment.paintable()))
paint_text_fragment(context, static_cast<TextPaintable const&>(fragment.paintable()), fragment, phase);
});
}

View File

@ -612,9 +612,9 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph
}
}
void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment)
void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
{
auto const& browsing_context = text_node.browsing_context();
auto const& browsing_context = paintable.browsing_context();
if (!browsing_context.is_focused_context())
return;
@ -622,7 +622,7 @@ void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_
if (!browsing_context.cursor_blink_state())
return;
if (browsing_context.cursor_position()->node() != &text_node.dom_node())
if (browsing_context.cursor_position()->node() != paintable.dom_node())
return;
// NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
@ -634,9 +634,9 @@ void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_
auto fragment_rect = fragment.absolute_rect();
auto text = text_node.text_for_rendering().bytes_as_string_view().substring_view(fragment.start(), fragment.length());
auto text = fragment.string_view();
CSSPixelRect cursor_rect {
fragment_rect.x() + CSSPixels::nearest_value_for(text_node.first_available_font().width(text.substring_view(0, text_node.browsing_context().cursor_position()->offset() - fragment.start()))),
fragment_rect.x() + CSSPixels::nearest_value_for(paintable.layout_node().first_available_font().width(text.substring_view(0, paintable.browsing_context().cursor_position()->offset() - fragment.start()))),
fragment_rect.top(),
1,
fragment_rect.height()
@ -644,10 +644,10 @@ void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_
auto cursor_device_rect = context.rounded_device_rect(cursor_rect).to_type<int>();
context.recording_painter().draw_rect(cursor_device_rect, text_node.computed_values().color());
context.recording_painter().draw_rect(cursor_device_rect, paintable.computed_values().color());
}
void paint_text_decoration(PaintContext& context, Layout::Node const& text_node, PaintableFragment const& fragment)
void paint_text_decoration(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment)
{
auto& painter = context.recording_painter();
auto& font = fragment.layout_node().first_available_font();
@ -655,11 +655,11 @@ void paint_text_decoration(PaintContext& context, Layout::Node const& text_node,
CSSPixels glyph_height = CSSPixels::nearest_value_for(font.pixel_size());
auto baseline = fragment.baseline();
auto line_color = text_node.computed_values().text_decoration_color();
auto line_color = paintable.computed_values().text_decoration_color();
auto const& text_paintable = static_cast<TextPaintable const&>(fragment.paintable());
auto device_line_thickness = context.rounded_device_pixels(text_paintable.text_decoration_thickness());
auto text_decoration_lines = text_node.computed_values().text_decoration_line();
auto text_decoration_lines = paintable.computed_values().text_decoration_line();
for (auto line : text_decoration_lines) {
DevicePixelPoint line_start_point {};
DevicePixelPoint line_end_point {};
@ -686,7 +686,7 @@ void paint_text_decoration(PaintContext& context, Layout::Node const& text_node,
return;
}
switch (text_node.computed_values().text_decoration_style()) {
switch (paintable.computed_values().text_decoration_style()) {
case CSS::TextDecorationStyle::Solid:
painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::Painter::LineStyle::Solid);
break;
@ -722,7 +722,7 @@ void paint_text_decoration(PaintContext& context, Layout::Node const& text_node,
}
}
void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment, PaintPhase phase)
void paint_text_fragment(PaintContext& context, TextPaintable const& paintable, PaintableFragment const& fragment, PaintPhase phase)
{
auto& painter = context.recording_painter();
@ -730,16 +730,16 @@ void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_nod
auto fragment_absolute_rect = fragment.absolute_rect();
auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
if (text_node.document().inspected_layout_node() == &text_node)
if (paintable.document().inspected_layout_node() == &paintable.layout_node())
context.recording_painter().draw_rect(fragment_absolute_device_rect.to_type<int>(), Color::Magenta);
auto text = text_node.text_for_rendering();
auto text = paintable.text_for_rendering();
DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
auto scale = context.device_pixels_per_css_pixel();
painter.draw_text_run(baseline_start.to_type<int>(), fragment.glyph_run(), text_node.computed_values().color(), fragment_absolute_device_rect.to_type<int>(), scale);
painter.draw_text_run(baseline_start.to_type<int>(), fragment.glyph_run(), paintable.computed_values().color(), fragment_absolute_device_rect.to_type<int>(), scale);
auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(text_node.first_available_font())).to_type<int>();
auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(paintable.layout_node().first_available_font())).to_type<int>();
if (!selection_rect.is_empty()) {
painter.fill_rect(selection_rect, CSS::SystemColor::highlight());
RecordingPainterStateSaver saver(painter);
@ -747,8 +747,8 @@ void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_nod
painter.draw_text_run(baseline_start.to_type<int>(), fragment.glyph_run(), CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>(), scale);
}
paint_text_decoration(context, text_node, fragment);
paint_cursor_if_needed(context, text_node, fragment);
paint_text_decoration(context, paintable, fragment);
paint_cursor_if_needed(context, paintable, fragment);
}
}
@ -811,8 +811,8 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
context.rounded_device_point(fragment_absolute_rect.top_left().translated(0, fragment.baseline())).to_type<int>(),
context.rounded_device_point(fragment_absolute_rect.top_right().translated(-1, fragment.baseline())).to_type<int>(), Color::Red);
}
if (is<Layout::TextNode>(fragment.layout_node()))
paint_text_fragment(context, static_cast<Layout::TextNode const&>(fragment.layout_node()), fragment, phase);
if (is<TextPaintable>(fragment.paintable()))
paint_text_fragment(context, static_cast<TextPaintable const&>(fragment.paintable()), fragment, phase);
}
if (should_clip_overflow) {

View File

@ -314,8 +314,8 @@ private:
Vector<PaintableFragment> m_fragments;
};
void paint_text_decoration(PaintContext& context, Layout::Node const& text_node, PaintableFragment const& fragment);
void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment);
void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment, PaintPhase phase);
void paint_text_decoration(PaintContext&, TextPaintable const&, PaintableFragment const&);
void paint_cursor_if_needed(PaintContext&, TextPaintable const&, PaintableFragment const&);
void paint_text_fragment(PaintContext&, TextPaintable const&, PaintableFragment const&, PaintPhase);
}

View File

@ -7,6 +7,7 @@
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/TextPaintable.h>
namespace Web::Painting {
@ -33,11 +34,11 @@ CSSPixelRect const PaintableFragment::absolute_rect() const
int PaintableFragment::text_index_at(CSSPixels x) const
{
if (!is<Layout::TextNode>(*m_layout_node))
if (!is<TextPaintable>(paintable()))
return 0;
auto& layout_text = verify_cast<Layout::TextNode>(layout_node());
auto& font = layout_text.first_available_font();
Utf8View view(layout_text.text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length));
Utf8View view(string_view());
CSSPixels relative_x = x - absolute_rect().x();
CSSPixels glyph_spacing = font.glyph_spacing();
@ -67,9 +68,6 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
if (paintable().selection_state() == Paintable::SelectionState::Full)
return absolute_rect();
if (!is<Layout::TextNode>(layout_node()))
return {};
auto selection = paintable().document().get_selection();
if (!selection)
return {};
@ -81,8 +79,7 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
auto const start_index = static_cast<unsigned>(m_start);
auto const end_index = static_cast<unsigned>(m_start) + static_cast<unsigned>(m_length);
auto& layout_text = verify_cast<Layout::TextNode>(layout_node());
auto text = layout_text.text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
auto text = string_view();
if (paintable().selection_state() == Paintable::SelectionState::StartAndEnd) {
// we are in the start/end node (both the same)
@ -140,4 +137,11 @@ CSSPixelRect PaintableFragment::selection_rect(Gfx::Font const& font) const
return {};
}
StringView PaintableFragment::string_view() const
{
if (!is<TextPaintable>(paintable()))
return {};
return static_cast<TextPaintable const&>(paintable()).text_for_rendering().bytes_as_string_view().substring_view(m_start, m_length);
}
}

View File

@ -47,6 +47,8 @@ public:
int text_index_at(CSSPixels) const;
StringView string_view() const;
private:
JS::NonnullGCPtr<Layout::Node const> m_layout_node;
CSSPixelPoint m_offset;

View File

@ -12,13 +12,14 @@
namespace Web::Painting {
JS::NonnullGCPtr<TextPaintable> TextPaintable::create(Layout::TextNode const& layout_node)
JS::NonnullGCPtr<TextPaintable> TextPaintable::create(Layout::TextNode const& layout_node, String const& text_for_rendering)
{
return layout_node.heap().allocate_without_realm<TextPaintable>(layout_node);
return layout_node.heap().allocate_without_realm<TextPaintable>(layout_node, text_for_rendering);
}
TextPaintable::TextPaintable(Layout::TextNode const& layout_node)
TextPaintable::TextPaintable(Layout::TextNode const& layout_node, String const& text_for_rendering)
: Paintable(layout_node)
, m_text_for_rendering(text_for_rendering)
{
}

View File

@ -14,7 +14,7 @@ class TextPaintable final : public Paintable {
JS_CELL(TextPaintable, Paintable);
public:
static JS::NonnullGCPtr<TextPaintable> create(Layout::TextNode const&);
static JS::NonnullGCPtr<TextPaintable> create(Layout::TextNode const&, String const& text_for_rendering);
Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }
@ -27,11 +27,14 @@ public:
void set_text_decoration_thickness(CSSPixels thickness) { m_text_decoration_thickness = thickness; }
CSSPixels text_decoration_thickness() const { return m_text_decoration_thickness; }
String const& text_for_rendering() const { return m_text_for_rendering; }
private:
virtual bool is_text_paintable() const override { return true; }
explicit TextPaintable(Layout::TextNode const&);
TextPaintable(Layout::TextNode const&, String const& text_for_rendering);
String m_text_for_rendering;
CSSPixels m_text_decoration_thickness { 0 };
};