mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-18 16:57:29 +03:00
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:
parent
2be47c3d7a
commit
dd8504c68d
Notes:
sideshowbarker
2024-07-17 01:13:25 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/dd8504c68d Pull-request: https://github.com/SerenityOS/serenity/pull/23626
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user