LibWeb: Use LineBuilder in IFC to layout line boxes incrementally

This resolves a long-standing architectural problem in LibWeb that made
it unable to place CSS floating objects correctly due to not having
final vertical position information when computing the amount of
available horizontal space for each line.
This commit is contained in:
Andreas Kling 2022-01-19 11:59:44 +01:00
parent 00bde9ca51
commit ce8043c6c2
Notes: sideshowbarker 2024-07-17 20:26:06 +09:00
2 changed files with 32 additions and 107 deletions

View file

@ -12,6 +12,7 @@
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/InlineFormattingContext.h>
#include <LibWeb/Layout/InlineLevelIterator.h>
#include <LibWeb/Layout/LineBuilder.h>
#include <LibWeb/Layout/ReplacedBox.h>
namespace Web::Layout {
@ -25,17 +26,13 @@ InlineFormattingContext::~InlineFormattingContext()
{
}
InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::available_space_for_line(size_t line_index) const
InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::available_space_for_line(float y) const
{
if (!parent()->is_block_formatting_context())
return { 0, context_box().width() };
AvailableSpaceForLineInfo info;
// FIXME: This is a total hack guess since we don't actually know the final y position of lines here!
float line_height = containing_block().line_height();
float y = (line_index * line_height);
auto const& bfc = static_cast<BlockFormattingContext const&>(*parent());
for (ssize_t i = bfc.left_floating_boxes().size() - 1; i >= 0; --i) {
@ -61,12 +58,6 @@ InlineFormattingContext::AvailableSpaceForLineInfo InlineFormattingContext::avai
return info;
}
float InlineFormattingContext::available_width_at_line(size_t line_index) const
{
auto info = available_space_for_line(line_index);
return info.right - info.left;
}
void InlineFormattingContext::run(Box&, LayoutMode layout_mode)
{
VERIFY(containing_block().children_are_inline());
@ -81,92 +72,34 @@ void InlineFormattingContext::run(Box&, LayoutMode layout_mode)
}
});
auto text_align = containing_block().computed_values().text_align();
float min_line_height = containing_block().line_height();
float max_line_width = 0;
float content_height = 0;
float max_linebox_width = 0;
for (size_t line_index = 0; line_index < containing_block().line_boxes().size(); ++line_index) {
auto& line_box = containing_block().line_boxes()[line_index];
for (auto& line_box : containing_block().line_boxes()) {
float max_height = min_line_height;
for (auto& fragment : line_box.fragments()) {
max_height = max(max_height, fragment.height());
}
float x_offset = available_space_for_line(line_index).left;
float excess_horizontal_space = (float)containing_block().width() - line_box.width();
switch (text_align) {
case CSS::TextAlign::Center:
case CSS::TextAlign::LibwebCenter:
x_offset += excess_horizontal_space / 2;
break;
case CSS::TextAlign::Right:
x_offset += excess_horizontal_space;
break;
case CSS::TextAlign::Left:
case CSS::TextAlign::Justify:
default:
break;
}
float excess_horizontal_space_including_whitespace = excess_horizontal_space;
int whitespace_count = 0;
if (text_align == CSS::TextAlign::Justify) {
for (auto& fragment : line_box.fragments()) {
if (fragment.is_justifiable_whitespace()) {
++whitespace_count;
excess_horizontal_space_including_whitespace += fragment.width();
}
}
}
float justified_space_width = whitespace_count ? (excess_horizontal_space_including_whitespace / (float)whitespace_count) : 0;
for (size_t i = 0; i < line_box.fragments().size(); ++i) {
auto& fragment = line_box.fragments()[i];
if (fragment.type() == LineBoxFragment::Type::Leading || fragment.type() == LineBoxFragment::Type::Trailing) {
fragment.set_height(max_height);
}
// Vertically align everyone's bottom to the line.
// FIXME: Support other kinds of vertical alignment.
fragment.set_offset({ roundf(x_offset + fragment.offset().x()), content_height + (max_height - fragment.height()) });
if (text_align == CSS::TextAlign::Justify
&& fragment.is_justifiable_whitespace()
&& 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) {
auto offset = line_box.fragments()[j].offset();
offset.translate_by(diff, 0);
line_box.fragments()[j].set_offset(offset);
}
}
}
if (!line_box.fragments().is_empty()) {
float left_edge = line_box.fragments().first().offset().x();
float right_edge = line_box.fragments().last().offset().x() + line_box.fragments().last().width();
float final_line_box_width = right_edge - left_edge;
line_box.m_width = final_line_box_width;
max_linebox_width = max(max_linebox_width, final_line_box_width);
}
max_line_width = max(max_line_width, line_box.width());
content_height += max_height;
}
if (layout_mode != LayoutMode::Default) {
containing_block().set_width(max_linebox_width);
containing_block().set_width(max_line_width);
}
containing_block().set_height(content_height);
}
float InlineFormattingContext::available_width_at_line(size_t line_index) const
{
// TODO: Remove this function, along with the old-style splitting functions.
VERIFY_NOT_REACHED();
auto info = available_space_for_line(line_index);
return info.right - info.left;
}
void InlineFormattingContext::dimension_box_on_line(Box& box, LayoutMode layout_mode)
{
if (is<ReplacedBox>(box)) {
@ -224,48 +157,40 @@ void InlineFormattingContext::dimension_box_on_line(Box& box, LayoutMode layout_
void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode)
{
auto& line_boxes = containing_block().line_boxes();
line_boxes.clear();
containing_block().line_boxes().clear();
containing_block().ensure_last_line_box();
InlineLevelIterator iterator(containing_block(), layout_mode);
float available_width = containing_block().width();
LineBuilder line_builder(*this);
for (;;) {
auto item_opt = iterator.next(available_width);
auto item_opt = iterator.next(line_builder.available_width_for_current_line());
if (!item_opt.has_value())
break;
auto& item = item_opt.value();
float current_line_width = line_boxes.last().width();
line_builder.break_if_needed(layout_mode, item.width);
bool should_break_here = layout_mode == LayoutMode::AllPossibleLineBreaks || (current_line_width + item.width) > available_width;
if (should_break_here)
line_boxes.append(LineBox());
if (item.type == InlineLevelIterator::Item::Type::ForcedBreak) {
line_boxes.append(LineBox());
continue;
}
if (item.type == InlineLevelIterator::Item::Type::Element) {
switch (item.type) {
case InlineLevelIterator::Item::Type::ForcedBreak:
line_builder.break_line();
break;
case InlineLevelIterator::Item::Type::Element: {
auto& box = verify_cast<Layout::Box>(*item.node);
dimension_box_on_line(box, LayoutMode::Default);
line_boxes.last().add_fragment(box, 0, 0, box.width(), box.height());
continue;
dimension_box_on_line(box, layout_mode);
line_builder.append_box(box);
break;
}
if (item.type == InlineLevelIterator::Item::Type::Text) {
case InlineLevelIterator::Item::Type::Text: {
auto& text_node = verify_cast<Layout::TextNode>(*item.node);
line_boxes.last().add_fragment(
line_builder.append_text_chunk(
text_node,
item.offset_in_node,
item.length_in_node,
item.width,
text_node.font().glyph_height());
continue;
break;
}
}
}

View file

@ -27,14 +27,14 @@ public:
void dimension_box_on_line(Box&, LayoutMode);
private:
struct AvailableSpaceForLineInfo {
float left { 0 };
float right { 0 };
};
AvailableSpaceForLineInfo available_space_for_line(size_t line_index) const;
AvailableSpaceForLineInfo available_space_for_line(float y) const;
private:
void generate_line_boxes(LayoutMode);
};