1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 16:55:09 +00:00

LibWeb: Rework the layout engine to use relative offsets

The box tree and line boxes now all store a relative offset from their
containing block, instead of an absolute (document-relative) position.

This removes a huge pain point from the layout system which was having
to adjust offsets recursively when something moved. It also makes some
layout logic significantly simpler.

Every box can still find its absolute position by walking its chain
of containing blocks and accumulating the translation from the root.
This is currently what we do both for rendering and hit testing.
This commit is contained in:
Andreas Kling 2020-06-10 10:42:29 +02:00
parent e836f09094
commit 656b01eb0f
21 changed files with 183 additions and 119 deletions

View file

@ -110,8 +110,8 @@ void dump_tree(const LayoutNode& layout_node)
layout_box.class_name(),
tag_name.characters(),
identifier.characters(),
layout_box.x(),
layout_box.y(),
layout_box.absolute_x(),
layout_box.absolute_y(),
layout_box.width(),
layout_box.height());
@ -158,7 +158,7 @@ void dump_tree(const LayoutNode& layout_node)
&fragment.layout_node(),
fragment.start(),
fragment.length(),
fragment.rect().to_string().characters());
fragment.absolute_rect().to_string().characters());
if (fragment.layout_node().is_text()) {
for (size_t i = 0; i < indent; ++i)
dbgprintf(" ");

View file

@ -47,6 +47,8 @@ class HTMLImageElement;
class HTMLScriptElement;
class PageView;
class ImageData;
class LineBox;
class LineBoxFragment;
class LayoutBlock;
class LayoutDocument;
class LayoutNode;
@ -57,6 +59,7 @@ class Node;
class Origin;
class Page;
class PageClient;
class RenderingContext;
class Resource;
class ResourceLoader;
class Selector;

View file

@ -86,7 +86,7 @@ void LayoutBlock::layout_block_children(LayoutMode layout_mode)
child_block.layout(layout_mode);
if (!child_block.is_absolutely_positioned())
content_height = child_block.rect().bottom() + child_block.box_model().full_margin(*this).bottom - rect().top();
content_height = max(content_height, child_block.effective_offset().y() + child_block.height() + child_block.box_model().full_margin(*this).bottom);
});
if (layout_mode != LayoutMode::Default) {
float max_width = 0;
@ -94,9 +94,9 @@ void LayoutBlock::layout_block_children(LayoutMode layout_mode)
if (child.is_box() && !child.is_absolutely_positioned())
max_width = max(max_width, to<LayoutBox>(child).width());
});
rect().set_width(max_width);
set_width(max_width);
}
rect().set_height(content_height);
set_height(content_height);
}
void LayoutBlock::layout_inline_children(LayoutMode layout_mode)
@ -133,10 +133,10 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode)
for (auto& line_box : m_line_boxes) {
float max_height = min_line_height;
for (auto& fragment : line_box.fragments()) {
max_height = max(max_height, fragment.rect().height());
max_height = max(max_height, fragment.height());
}
float x_offset = x();
float x_offset = 0;
float excess_horizontal_space = (float)width() - line_box.width();
switch (text_align) {
@ -158,7 +158,7 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode)
for (auto& fragment : line_box.fragments()) {
if (fragment.is_justifiable_whitespace()) {
++whitespace_count;
excess_horizontal_space_including_whitespace += fragment.rect().width();
excess_horizontal_space_including_whitespace += fragment.width();
}
}
}
@ -173,34 +173,32 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode)
// Vertically align everyone's bottom to the line.
// FIXME: Support other kinds of vertical alignment.
fragment.rect().set_x(roundf(x_offset + fragment.rect().x()));
fragment.rect().set_y(y() + content_height + (max_height - fragment.rect().height()) - (line_spacing / 2));
fragment.set_offset({ roundf(x_offset + fragment.offset().x()), content_height + (max_height - fragment.height()) - (line_spacing / 2) });
if (text_align == CSS::ValueID::Justify) {
if (fragment.is_justifiable_whitespace()) {
if (fragment.rect().width() != justified_space_width) {
float diff = justified_space_width - fragment.rect().width();
fragment.rect().set_width(justified_space_width);
if (fragment.width() != justified_space_width) {
float diff = justified_space_width - fragment.width();
fragment.set_width(justified_space_width);
// Shift subsequent sibling fragments to the right to adjust for change in width.
for (size_t j = i + 1; j < line_box.fragments().size(); ++j) {
line_box.fragments()[j].rect().move_by(diff, 0);
auto offset = line_box.fragments()[j].offset();
offset.move_by(diff, 0);
line_box.fragments()[j].set_offset(offset);
}
}
}
}
if (is<LayoutReplaced>(fragment.layout_node()))
const_cast<LayoutReplaced&>(to<LayoutReplaced>(fragment.layout_node())).set_rect(fragment.rect());
if (fragment.layout_node().is_inline_block()) {
auto& inline_block = const_cast<LayoutBlock&>(to<LayoutBlock>(fragment.layout_node()));
inline_block.set_rect(fragment.rect());
inline_block.set_size(fragment.size());
inline_block.layout(layout_mode);
}
float final_line_box_width = 0;
for (auto& fragment : line_box.fragments())
final_line_box_width += fragment.rect().width();
final_line_box_width += fragment.width();
line_box.m_width = final_line_box_width;
max_linebox_width = max(max_linebox_width, final_line_box_width);
@ -210,10 +208,10 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode)
}
if (layout_mode != LayoutMode::Default) {
rect().set_width(max_linebox_width);
set_width(max_linebox_width);
}
rect().set_height(content_height);
set_height(content_height);
}
void LayoutBlock::compute_width()
@ -368,7 +366,7 @@ void LayoutBlock::compute_width()
}
}
rect().set_width(used_width.to_px(*this));
set_width(used_width.to_px(*this));
box_model().margin().left = margin_left;
box_model().margin().right = margin_right;
box_model().border().left = border_left;
@ -404,11 +402,6 @@ void LayoutBlock::compute_position()
+ box_model().padding().left.to_px(*this)
+ box_model().offset().left.to_px(*this);
if (style.position() != CSS::Position::Absolute || containing_block.style().position() == CSS::Position::Absolute)
position_x += containing_block.x();
rect().set_x(position_x);
float position_y = box_model().full_margin(*this).top
+ box_model().offset().top.to_px(*this);
@ -420,17 +413,14 @@ void LayoutBlock::compute_position()
relevant_sibling = relevant_sibling->previous_sibling();
}
if (relevant_sibling == nullptr) {
position_y += containing_block.y();
} else {
auto& previous_sibling_rect = relevant_sibling->rect();
if (relevant_sibling) {
auto& previous_sibling_style = relevant_sibling->box_model();
position_y += previous_sibling_rect.y() + previous_sibling_rect.height();
position_y += relevant_sibling->effective_offset().y() + relevant_sibling->height();
position_y += previous_sibling_style.full_margin(*this).bottom;
}
}
rect().set_y(position_y);
set_offset({ position_x, position_y });
}
void LayoutBlock::compute_height()
@ -438,7 +428,7 @@ void LayoutBlock::compute_height()
auto& style = this->style();
auto height = style.length_or_fallback(CSS::PropertyID::Height, Length(), containing_block()->height());
if (height.is_absolute())
rect().set_height(height.to_px(*this));
set_height(height.to_px(*this));
}
void LayoutBlock::render(RenderingContext& context)
@ -452,7 +442,7 @@ void LayoutBlock::render(RenderingContext& context)
for (auto& line_box : m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (context.should_show_line_box_borders())
context.painter().draw_rect(enclosing_int_rect(fragment.rect()), Color::Green);
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green);
fragment.render(context);
}
}
@ -467,7 +457,7 @@ HitTestResult LayoutBlock::hit_test(const Gfx::Point& position) const
HitTestResult result;
for (auto& line_box : m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (enclosing_int_rect(fragment.rect()).contains(position)) {
if (enclosing_int_rect(fragment.absolute_rect()).contains(position)) {
if (fragment.layout_node().is_block())
return to<LayoutBlock>(fragment.layout_node()).hit_test(position);
return { fragment.layout_node(), fragment.text_index_at(position.x()) };
@ -477,7 +467,7 @@ HitTestResult LayoutBlock::hit_test(const Gfx::Point& position) const
// FIXME: This should be smarter about the text position if we're hitting a block
// that has text inside it, but `position` is to the right of the text box.
return { rect().contains(position.x(), position.y()) ? this : nullptr };
return { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
}
NonnullRefPtr<StyleProperties> LayoutBlock::style_for_anonymous_block() const

View file

@ -203,9 +203,9 @@ void LayoutBox::render(RenderingContext& context)
#endif
Gfx::FloatRect padded_rect;
padded_rect.set_x(x() - box_model().padding().left.to_px(*this));
padded_rect.set_x(absolute_x() - box_model().padding().left.to_px(*this));
padded_rect.set_width(width() + box_model().padding().left.to_px(*this) + box_model().padding().right.to_px(*this));
padded_rect.set_y(y() - box_model().padding().top.to_px(*this));
padded_rect.set_y(absolute_y() - box_model().padding().top.to_px(*this));
padded_rect.set_height(height() + box_model().padding().top.to_px(*this) + box_model().padding().bottom.to_px(*this));
if (!is_body()) {
@ -237,7 +237,7 @@ void LayoutBox::render(RenderingContext& context)
LayoutNodeWithStyleAndBoxModelMetrics::render(context);
if (node() && document().inspected_node() == node())
context.painter().draw_rect(enclosing_int_rect(m_rect), Color::Magenta);
context.painter().draw_rect(enclosing_int_rect(absolute_rect()), Color::Magenta);
}
HitTestResult LayoutBox::hit_test(const Gfx::Point& position) const
@ -245,7 +245,7 @@ HitTestResult LayoutBox::hit_test(const Gfx::Point& position) const
// FIXME: It would be nice if we could confidently skip over hit testing
// parts of the layout tree, but currently we can't just check
// m_rect.contains() since inline text rects can't be trusted..
HitTestResult result { m_rect.contains(position.x(), position.y()) ? this : nullptr };
HitTestResult result { absolute_rect().contains(position.x(), position.y()) ? this : nullptr };
for_each_child([&](auto& child) {
auto child_result = child.hit_test(position);
if (child_result.layout_node)
@ -260,7 +260,7 @@ void LayoutBox::set_needs_display()
ASSERT(frame);
if (!is_inline()) {
const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(rect()));
const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(absolute_rect()));
return;
}
@ -272,12 +272,41 @@ bool LayoutBox::is_body() const
return node() && node() == document().body();
}
void LayoutBox::set_rect(const Gfx::FloatRect& rect)
void LayoutBox::set_offset(const Gfx::FloatPoint& offset)
{
if (m_rect == rect)
if (m_offset == offset)
return;
m_rect = rect;
m_offset = offset;
did_set_rect();
}
void LayoutBox::set_size(const Gfx::FloatSize& size)
{
if (m_size == size)
return;
m_size = size;
did_set_rect();
}
Gfx::FloatPoint LayoutBox::effective_offset() const
{
if (m_containing_line_box_fragment)
return m_containing_line_box_fragment->offset();
return m_offset;
}
const Gfx::FloatRect LayoutBox::absolute_rect() const
{
Gfx::FloatRect rect { effective_offset(), size() };
for (auto* block = containing_block(); block; block = block->containing_block()) {
rect.move_by(block->effective_offset());
}
return rect;
}
void LayoutBox::set_containing_line_box_fragment(LineBoxFragment& fragment)
{
m_containing_line_box_fragment = fragment.make_weak_ptr();
}
}

View file

@ -33,22 +33,33 @@ namespace Web {
class LayoutBox : public LayoutNodeWithStyleAndBoxModelMetrics {
public:
const Gfx::FloatRect& rect() const { return m_rect; }
Gfx::FloatRect& rect() { return m_rect; }
void set_rect(const Gfx::FloatRect&);
const Gfx::FloatRect absolute_rect() const;
float x() const { return rect().x(); }
float y() const { return rect().y(); }
float width() const { return rect().width(); }
float height() const { return rect().height(); }
Gfx::FloatSize size() const { return rect().size(); }
Gfx::FloatPoint position() const { return rect().location(); }
Gfx::FloatPoint effective_offset() const;
virtual HitTestResult hit_test(const Gfx::Point& position) const override;
void set_offset(const Gfx::FloatPoint& offset);
void set_offset(float x, float y) { set_offset({ x, y }); }
const Gfx::FloatSize& size() const { return m_size; }
void set_size(const Gfx::FloatSize&);
void set_size(float width, float height) { set_size({ width, height }); }
void set_width(float width) { set_size(width, height()); }
void set_height(float height) { set_size(width(), height); }
float width() const { return m_size.width(); }
float height() const { return m_size.height(); }
float absolute_x() const { return absolute_rect().x(); }
float absolute_y() const { return absolute_rect().y(); }
Gfx::FloatPoint absolute_position() const { return absolute_rect().location(); }
virtual HitTestResult hit_test(const Gfx::Point& absolute_position) const override;
virtual void set_needs_display() override;
bool is_body() const;
void set_containing_line_box_fragment(LineBoxFragment&);
protected:
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)
: LayoutNodeWithStyleAndBoxModelMetrics(node, move(style))
@ -70,7 +81,11 @@ private:
};
void paint_border(RenderingContext&, Edge, const Gfx::FloatRect&, CSS::PropertyID style_property_id, CSS::PropertyID color_property_id, CSS::PropertyID width_property_id);
Gfx::FloatRect m_rect;
Gfx::FloatPoint m_offset;
Gfx::FloatSize m_size;
// Some boxes hang off of line box fragments. (inline-block, inline-table, replaced, etc)
WeakPtr<LineBoxFragment> m_containing_line_box_fragment;
};
template<>

View file

@ -55,11 +55,11 @@ void LayoutCanvas::render(RenderingContext& context)
return;
// FIXME: This should be done at a different level. Also rect() does not include padding etc!
if (!context.viewport_rect().intersects(enclosing_int_rect(rect())))
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
if (node().bitmap())
context.painter().draw_scaled_bitmap(enclosing_int_rect(rect()), *node().bitmap(), node().bitmap()->rect());
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
LayoutReplaced::render(context);
}

View file

@ -28,6 +28,7 @@
#include <LibWeb/Frame/Frame.h>
#include <LibWeb/Layout/LayoutDocument.h>
#include <LibWeb/Layout/LayoutImage.h>
#include <LibWeb/Layout/LayoutWidget.h>
namespace Web {
@ -43,27 +44,33 @@ LayoutDocument::~LayoutDocument()
void LayoutDocument::layout(LayoutMode layout_mode)
{
ASSERT(document().frame());
rect().set_width(document().frame()->size().width());
set_width(document().frame()->size().width());
LayoutNode::layout(layout_mode);
ASSERT(!children_are_inline());
int lowest_bottom = 0;
float lowest_bottom = 0;
for_each_child([&](auto& child) {
ASSERT(is<LayoutBlock>(child));
auto& child_block = to<LayoutBlock>(child);
if (child_block.rect().bottom() > lowest_bottom)
lowest_bottom = child_block.rect().bottom();
lowest_bottom = max(lowest_bottom, child_block.absolute_rect().bottom());
});
set_height(lowest_bottom);
// FIXME: This is a total hack. Make sure any GUI::Widgets are moved into place after layout.
// We should stop embedding GUI::Widgets entirely, since that won't work out-of-process.
for_each_in_subtree_of_type<LayoutWidget>([&](auto& widget) {
widget.update_widget();
return IterationDecision::Continue;
});
rect().set_bottom(lowest_bottom);
}
void LayoutDocument::did_set_viewport_rect(Badge<Frame>, const Gfx::Rect& a_viewport_rect)
{
Gfx::FloatRect viewport_rect(a_viewport_rect.x(), a_viewport_rect.y(), a_viewport_rect.width(), a_viewport_rect.height());
for_each_in_subtree_of_type<LayoutImage>([&](auto& layout_image) {
const_cast<HTMLImageElement&>(layout_image.node()).set_visible_in_viewport({}, viewport_rect.intersects(layout_image.rect()));
const_cast<HTMLImageElement&>(layout_image.node()).set_visible_in_viewport({}, viewport_rect.intersects(layout_image.absolute_rect()));
return IterationDecision::Continue;
});
}

View file

@ -67,8 +67,8 @@ void LayoutFrame::render(RenderingContext& context)
context.painter().save();
auto old_viewport_rect = context.viewport_rect();
context.painter().add_clip_rect(enclosing_int_rect(rect()));
context.painter().translate(x(), y());
context.painter().add_clip_rect(enclosing_int_rect(absolute_rect()));
context.painter().translate(absolute_x(), absolute_y());
context.set_viewport_rect({ {}, node().hosted_frame()->size() });
node().hosted_frame()->document()->layout_node()->render(context);
@ -82,7 +82,7 @@ void LayoutFrame::did_set_rect()
LayoutReplaced::did_set_rect();
ASSERT(node().hosted_frame());
node().hosted_frame()->set_size(Gfx::Size(rect().width(), rect().height()));
node().hosted_frame()->set_size(size().to_int_size());
}
}

View file

@ -52,11 +52,11 @@ void LayoutImage::layout(LayoutMode layout_mode)
auto alt = node().alt();
if (alt.is_empty())
alt = node().src();
rect().set_width(font.width(alt) + 16);
rect().set_height(font.glyph_height() + 16);
set_width(font.width(alt) + 16);
set_height(font.glyph_height() + 16);
} else {
rect().set_width(16);
rect().set_height(16);
set_width(16);
set_height(16);
}
LayoutReplaced::layout(layout_mode);
@ -68,18 +68,18 @@ void LayoutImage::render(RenderingContext& context)
return;
// FIXME: This should be done at a different level. Also rect() does not include padding etc!
if (!context.viewport_rect().intersects(enclosing_int_rect(rect())))
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
if (renders_as_alt_text()) {
context.painter().set_font(Gfx::Font::default_font());
Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
auto alt = node().alt();
if (alt.is_empty())
alt = node().src();
context.painter().draw_text(enclosing_int_rect(rect()), alt, Gfx::TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
context.painter().draw_text(enclosing_int_rect(absolute_rect()), alt, Gfx::TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
} else if (node().bitmap())
context.painter().draw_scaled_bitmap(enclosing_int_rect(rect()), *node().bitmap(), node().bitmap()->rect());
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
LayoutReplaced::render(context);
}

View file

@ -58,8 +58,8 @@ void LayoutListItem::layout(LayoutMode layout_mode)
append_child(*m_marker);
}
Gfx::FloatRect marker_rect { x() - 8, y(), 4, height() };
m_marker->set_rect(marker_rect);
m_marker->set_offset(-8, 0);
m_marker->set_size(4, height());
}
}

View file

@ -41,7 +41,7 @@ LayoutListItemMarker::~LayoutListItemMarker()
void LayoutListItemMarker::render(RenderingContext& context)
{
Gfx::Rect bullet_rect { 0, 0, 4, 4 };
bullet_rect.center_within(enclosing_int_rect(rect()));
bullet_rect.center_within(enclosing_int_rect(absolute_rect()));
// FIXME: It would be nicer to not have to go via the parent here to get our inherited style.
auto color = parent()->style().color_or_fallback(CSS::PropertyID::Color, document(), context.palette().base_text());
context.painter().fill_rect(bullet_rect, color);

View file

@ -155,7 +155,7 @@ void LayoutNode::set_needs_display()
if (auto* block = containing_block()) {
block->for_each_fragment([&](auto& fragment) {
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(fragment.rect()));
const_cast<Frame*>(frame)->set_needs_display(enclosing_int_rect(fragment.absolute_rect()));
}
return IterationDecision::Continue;
});
@ -172,13 +172,13 @@ float LayoutNode::font_size() const
Gfx::FloatPoint LayoutNode::box_type_agnostic_position() const
{
if (is_box())
return to<LayoutBox>(*this).position();
return to<LayoutBox>(*this).absolute_position();
ASSERT(is_inline());
Gfx::FloatPoint position;
if (auto* block = containing_block()) {
block->for_each_fragment([&](auto& fragment) {
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
position = fragment.rect().location();
position = fragment.absolute_rect().location();
return IterationDecision::Break;
}
return IterationDecision::Continue;

View file

@ -141,27 +141,24 @@ Gfx::FloatPoint LayoutReplaced::calculate_position()
box_model().padding().top = style.length_or_fallback(CSS::PropertyID::PaddingTop, zero_value, containing_block.width());
box_model().padding().bottom = style.length_or_fallback(CSS::PropertyID::PaddingBottom, zero_value, containing_block.width());
float position_x = box_model().margin().left.to_px(*this)
float x = box_model().margin().left.to_px(*this)
+ box_model().border().left.to_px(*this)
+ box_model().padding().left.to_px(*this)
+ box_model().offset().left.to_px(*this);
if (style.position() != CSS::Position::Absolute || containing_block.style().position() == CSS::Position::Absolute)
position_x += containing_block.x();
float y = box_model().full_margin(*this).top + box_model().offset().top.to_px(*this);
float position_y = box_model().full_margin(*this).top + box_model().offset().top.to_px(*this);
return { position_x, position_y };
return { x, y };
}
void LayoutReplaced::layout(LayoutMode layout_mode)
{
rect().set_width(calculate_width());
rect().set_height(calculate_height());
set_width(calculate_width());
set_height(calculate_height());
LayoutBox::layout(layout_mode);
rect().set_location(calculate_position());
set_offset(calculate_position());
}
void LayoutReplaced::split_into_lines(LayoutBlock& container, LayoutMode layout_mode)

View file

@ -53,7 +53,7 @@ void LayoutTableRowGroup::layout(LayoutMode layout_mode)
content_height += row.height();
});
rect().set_height(content_height);
set_height(content_height);
}
}

View file

@ -72,17 +72,17 @@ void LayoutText::render_fragment(RenderingContext& context, const LineBoxFragmen
auto background_color = style().property(CSS::PropertyID::BackgroundColor);
if (background_color.has_value() && background_color.value()->is_color())
painter.fill_rect(enclosing_int_rect(fragment.rect()), background_color.value()->to_color(document()));
painter.fill_rect(enclosing_int_rect(fragment.absolute_rect()), background_color.value()->to_color(document()));
auto color = style().color_or_fallback(CSS::PropertyID::Color, document(), context.palette().base_text());
auto text_decoration = style().string_or_fallback(CSS::PropertyID::TextDecoration, "none");
if (document().inspected_node() == &node())
context.painter().draw_rect(enclosing_int_rect(fragment.rect()), Color::Magenta);
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Magenta);
bool is_underline = text_decoration == "underline";
if (is_underline)
painter.draw_line(enclosing_int_rect(fragment.rect()).bottom_left().translated(0, 1), enclosing_int_rect(fragment.rect()).bottom_right().translated(0, 1), color);
painter.draw_line(enclosing_int_rect(fragment.absolute_rect()).bottom_left().translated(0, 1), enclosing_int_rect(fragment.absolute_rect()).bottom_right().translated(0, 1), color);
auto text = m_text_for_rendering;
auto text_transform = style().string_or_fallback(CSS::PropertyID::TextTransform, "none");
@ -91,7 +91,7 @@ void LayoutText::render_fragment(RenderingContext& context, const LineBoxFragmen
if (text_transform == "lowercase")
text = m_text_for_rendering.to_lowercase();
painter.draw_text(enclosing_int_rect(fragment.rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::TopLeft, color);
painter.draw_text(enclosing_int_rect(fragment.absolute_rect()), text.substring_view(fragment.start(), fragment.length()), Gfx::TextAlignment::TopLeft, color);
}
template<typename Callback>

View file

@ -59,7 +59,7 @@ void LayoutWidget::did_set_rect()
void LayoutWidget::update_widget()
{
auto adjusted_widget_position = rect().location().to_int_point();
auto adjusted_widget_position = absolute_rect().location().to_int_point();
auto& page_view = static_cast<const PageView&>(document().frame()->page().client());
adjusted_widget_position.move_by(-page_view.horizontal_scrollbar().value(), -page_view.vertical_scrollbar().value());
widget().move_to(adjusted_widget_position);

View file

@ -27,6 +27,7 @@
#include <AK/Utf8View.h>
#include <LibWeb/Layout/LayoutNode.h>
#include <LibWeb/Layout/LayoutText.h>
#include <LibWeb/Layout/LayoutBox.h>
#include <LibWeb/Layout/LineBox.h>
#include <ctype.h>
@ -39,18 +40,21 @@ void LineBox::add_fragment(const LayoutNode& layout_node, int start, int length,
// The fragment we're adding is from the last LayoutNode on the line.
// Expand the last fragment instead of adding a new one with the same LayoutNode.
m_fragments.last().m_length = (start - m_fragments.last().m_start) + length;
m_fragments.last().m_rect.set_width(m_fragments.last().m_rect.width() + width);
m_fragments.last().set_width(m_fragments.last().width() + width);
} else {
m_fragments.empend(layout_node, start, length, Gfx::FloatRect(m_width, 0, width, height));
m_fragments.append(make<LineBoxFragment>(layout_node, start, length, Gfx::FloatPoint(m_width, 0), Gfx::FloatSize(width, height)));
}
m_width += width;
if (is<LayoutBox>(layout_node))
const_cast<LayoutBox&>(to<LayoutBox>(layout_node)).set_containing_line_box_fragment(m_fragments.last());
}
void LineBox::trim_trailing_whitespace()
{
while (!m_fragments.is_empty() && m_fragments.last().is_justifiable_whitespace()) {
auto fragment = m_fragments.take_last();
m_width -= fragment.width();
m_width -= fragment->width();
}
if (m_fragments.is_empty())
@ -64,7 +68,7 @@ void LineBox::trim_trailing_whitespace()
int space_width = last_fragment.layout_node().style().font().glyph_width(' ');
while (last_fragment.length() && isspace(last_text[last_fragment.length() - 1])) {
last_fragment.m_length -= 1;
last_fragment.m_rect.set_width(last_fragment.m_rect.width() - space_width);
last_fragment.set_width(last_fragment.width() - space_width);
m_width -= space_width;
}
}

View file

@ -26,6 +26,7 @@
#pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/Vector.h>
#include <LibWeb/Layout/LineBoxFragment.h>
@ -39,13 +40,14 @@ public:
void add_fragment(const LayoutNode& layout_node, int start, int length, int width, int height);
const Vector<LineBoxFragment>& fragments() const { return m_fragments; }
Vector<LineBoxFragment>& fragments() { return m_fragments; }
const NonnullOwnPtrVector<LineBoxFragment>& fragments() const { return m_fragments; }
NonnullOwnPtrVector<LineBoxFragment>& fragments() { return m_fragments; }
void trim_trailing_whitespace();
private:
friend class LayoutBlock;
Vector<LineBoxFragment> m_fragments;
NonnullOwnPtrVector<LineBoxFragment> m_fragments;
float m_width { 0 };
};

View file

@ -57,6 +57,14 @@ StringView LineBoxFragment::text() const
return to<LayoutText>(layout_node()).text_for_rendering().substring_view(m_start, m_length);
}
const Gfx::FloatRect LineBoxFragment::absolute_rect() const
{
Gfx::FloatRect rect { {}, size() };
rect.set_location(m_layout_node.containing_block()->absolute_position());
rect.move_by(offset());
return rect;
}
int LineBoxFragment::text_index_at(float x) const
{
if (!layout_node().is_text())
@ -65,7 +73,7 @@ int LineBoxFragment::text_index_at(float x) const
auto& font = layout_text.style().font();
Utf8View view(text());
float relative_x = x - m_rect.location().x();
float relative_x = x - absolute_x();
float glyph_spacing = font.glyph_spacing();
float width_so_far = 0;

View file

@ -26,31 +26,39 @@
#pragma once
#include <AK/Weakable.h>
#include <LibGfx/FloatRect.h>
#include <LibWeb/Forward.h>
namespace Web {
class LayoutNode;
class RenderingContext;
class LineBoxFragment {
class LineBoxFragment : public Weakable<LineBoxFragment> {
friend class LineBox;
public:
LineBoxFragment(const LayoutNode& layout_node, int start, int length, const Gfx::FloatRect& rect)
LineBoxFragment(const LayoutNode& layout_node, int start, int length, const Gfx::FloatPoint& offset, const Gfx::FloatSize& size)
: m_layout_node(layout_node)
, m_start(start)
, m_length(length)
, m_rect(rect)
, m_offset(offset)
, m_size(size)
{
}
const LayoutNode& layout_node() const { return m_layout_node; }
int start() const { return m_start; }
int length() const { return m_length; }
const Gfx::FloatRect& rect() const { return m_rect; }
Gfx::FloatRect& rect() { return m_rect; }
const Gfx::FloatRect absolute_rect() const;
float width() const { return m_rect.width(); }
const Gfx::FloatPoint& offset() const { return m_offset; }
void set_offset(const Gfx::FloatPoint& offset) { m_offset = offset; }
const Gfx::FloatSize& size() const { return m_size; }
void set_width(float width) { m_size.set_width(width); }
float width() const { return m_size.width(); }
float height() const { return m_size.height(); }
float absolute_x() const { return absolute_rect().x(); }
void render(RenderingContext&);
@ -63,7 +71,8 @@ private:
const LayoutNode& m_layout_node;
int m_start { 0 };
int m_length { 0 };
Gfx::FloatRect m_rect;
Gfx::FloatPoint m_offset;
Gfx::FloatSize m_size;
};
}

View file

@ -161,14 +161,14 @@ void PageView::layout_and_sync_size()
page().main_frame().set_size(available_size());
document()->layout();
set_content_size(enclosing_int_rect(layout_root()->rect()).size());
set_content_size(layout_root()->size().to_int_size());
// NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again
// since the scrollbars now take up some of the available space.
if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) {
page().main_frame().set_size(available_size());
document()->layout();
set_content_size(enclosing_int_rect(layout_root()->rect()).size());
set_content_size(layout_root()->size().to_int_size());
}
page().main_frame().set_viewport_rect(viewport_rect_in_content_coordinates());