Skip to content

Commit

Permalink
LibWeb: Align actual baselines, not just bottoms
Browse files Browse the repository at this point in the history
Until now, we've been treating the bottom of every line box fragment as
its baseline, and just aligning all the bottoms to the bottom of the
line box. That gave decent results in many cases, but was not correct.

This patch starts moving towards actual baseline calculations as
specified by CSS2.

Note that once layout is finished with a line box, we also store the
baseline of the line box in LineBox::m_baseline. This allows us to align
the real baseline of display:inline-block elements with other inline
content on the same line.
  • Loading branch information
awesomekling committed Mar 3, 2022
1 parent 1d6cf3a commit 7dae895
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 32 deletions.
5 changes: 3 additions & 2 deletions Userland/Libraries/LibWeb/Dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,13 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
auto& line_box = block.line_boxes()[line_box_index];
for (size_t i = 0; i < indent; ++i)
builder.append(" ");
builder.appendff(" {}line {}{} width: {}, bottom: {}\n",
builder.appendff(" {}line {}{} width: {}, bottom: {}, baseline: {}\n",
line_box_color_on,
line_box_index,
color_off,
line_box.width(),
line_box.bottom());
line_box.bottom(),
line_box.baseline());
for (size_t fragment_index = 0; fragment_index < line_box.fragments().size(); ++fragment_index) {
auto& fragment = line_box.fragments()[fragment_index];
for (size_t i = 0; i < indent; ++i)
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class ButtonBox;
class CheckBox;
class FlexFormattingContext;
class FormattingContext;
class FormattingState;
class InitialContainingBlock;
class InlineFormattingContext;
class Label;
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibWeb/Layout/LineBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class LineBox {

float width() const { return m_width; }
float bottom() const { return m_bottom; }
float baseline() const { return m_baseline; }

void add_fragment(Node const& layout_node, int start, int length, float leading_size, float trailing_size, float content_width, float content_height, float border_box_top, float border_box_bottom, LineBoxFragment::Type = LineBoxFragment::Type::Normal);

Expand All @@ -36,6 +37,7 @@ class LineBox {
Vector<LineBoxFragment> m_fragments;
float m_width { 0 };
float m_bottom { 0 };
float m_baseline { 0 };
};

}
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <AK/Utf8View.h>
#include <LibGfx/Painter.h>
#include <LibWeb/Layout/FormattingState.h>
#include <LibWeb/Layout/InitialContainingBlock.h>
#include <LibWeb/Layout/LineBoxFragment.h>
#include <LibWeb/Layout/TextNode.h>
Expand Down
85 changes: 55 additions & 30 deletions Userland/Libraries/LibWeb/Layout/LineBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void LineBuilder::append_box(Box const& box, float leading_size, float trailing_
auto& box_state = m_formatting_state.get_mutable(box);
auto& line_box = ensure_last_line_box();
line_box.add_fragment(box, 0, 0, leading_size, trailing_size, box_state.content_width, box_state.content_height, box_state.border_box_top(), box_state.border_box_bottom());
m_max_height_on_current_line = max(m_max_height_on_current_line, box_state.content_height);
m_max_height_on_current_line = max(m_max_height_on_current_line, box_state.border_box_height());

box_state.containing_line_box_fragment = LineBoxFragmentCoordinate {
.line_box_index = m_containing_block_state.line_boxes.size() - 1,
Expand Down Expand Up @@ -83,6 +83,19 @@ bool LineBuilder::should_break(LayoutMode layout_mode, float next_item_width, bo
return (current_line_width + next_item_width) > m_available_width_for_current_line;
}

static float box_baseline(FormattingState const& state, Box const& box)
{
auto const& box_state = state.get(box);
if (!box_state.line_boxes.is_empty())
return box_state.offset.y() + box_state.line_boxes.last().baseline();
if (box.has_children() && !box.children_are_inline()) {
auto const* child_box = box.last_child_of_type<Box>();
VERIFY(child_box);
return box_baseline(state, *child_box);
}
return box_state.border_box_height();
}

void LineBuilder::update_last_line()
{
m_last_line_needs_update = false;
Expand Down Expand Up @@ -125,32 +138,25 @@ void LineBuilder::update_last_line()

float justified_space_width = whitespace_count > 0 ? (excess_horizontal_space_including_whitespace / static_cast<float>(whitespace_count)) : 0;

// HACK: This is where we determine the baseline of this line box.
// We use the bottommost value of all the font baselines on the line and all the inline-block heights.
// FIXME: Support all the various CSS baseline properties, etc.
float max_height = max(m_max_height_on_current_line, m_context.containing_block().line_height());
for (auto const& fragment : line_box.fragments()) {
if (auto length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>(); length_percentage && length_percentage->is_length()) {
max_height = max(max_height, fragment.border_box_height() + length_percentage->length().to_px(fragment.layout_node()));
}
}
auto fragment_baseline = [&](auto const& fragment) -> float {
if (fragment.layout_node().is_text_node())
return fragment.layout_node().font().baseline();
auto const& box = verify_cast<Layout::Box>(fragment.layout_node());
return box_baseline(m_formatting_state, box);
};

float line_box_baseline = 0;
for (auto& fragment : line_box.fragments()) {
float extra_height_from_vertical_align = 0;
if (auto length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>(); length_percentage && length_percentage->is_length()) {
extra_height_from_vertical_align = length_percentage->length().to_px(fragment.layout_node());
auto line_box_baseline = [&] {
float line_box_baseline = 0;
for (auto const& fragment : line_box.fragments()) {
auto baseline = fragment_baseline(fragment);
// NOTE: For fragments with a <length> vertical-align, shift the fragment baseline down by the length.
// This ensures that we make enough vertical space on the line for any manually-aligned fragments.
if (auto length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>(); length_percentage && length_percentage->is_length())
baseline += length_percentage->length().to_px(fragment.layout_node());
line_box_baseline = max(line_box_baseline, baseline);
}
float fragment_baseline;
if (fragment.layout_node().is_box()) {
fragment_baseline = m_formatting_state.get(static_cast<Box const&>(fragment.layout_node())).border_box_height();
} else {
float font_baseline = fragment.layout_node().font().baseline();
fragment_baseline = max_height + (font_baseline / 2.0f);
}
fragment_baseline += extra_height_from_vertical_align;
line_box_baseline = max(line_box_baseline, fragment_baseline);
}
return line_box_baseline;
}();

// Now we're going to align our fragments on the inline axis.
// We need to remember how much the last fragment on the line was moved by this process,
Expand All @@ -163,12 +169,30 @@ void LineBuilder::update_last_line()
float new_fragment_x = roundf(x_offset + fragment.offset().x());
float new_fragment_y = 0;

if (auto length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>(); length_percentage && length_percentage->is_length()) {
new_fragment_y = m_current_y + (line_box_baseline - (fragment.border_box_bottom() + length_percentage->length().to_px(fragment.layout_node())));
auto y_value_for_alignment = [&](CSS::VerticalAlign vertical_align) {
switch (vertical_align) {
case CSS::VerticalAlign::Baseline:
case CSS::VerticalAlign::Bottom:
case CSS::VerticalAlign::Middle:
case CSS::VerticalAlign::Sub:
case CSS::VerticalAlign::Super:
case CSS::VerticalAlign::TextBottom:
case CSS::VerticalAlign::TextTop:
case CSS::VerticalAlign::Top:
// FIXME: These are all 'baseline'
return m_current_y + line_box_baseline - fragment_baseline(fragment) + fragment.border_box_top();
}
VERIFY_NOT_REACHED();
};

auto const& vertical_align = fragment.layout_node().computed_values().vertical_align();
if (vertical_align.has<CSS::VerticalAlign>()) {
new_fragment_y = y_value_for_alignment(vertical_align.get<CSS::VerticalAlign>());
} else {
// Vertically align everyone's bottom to the baseline.
// FIXME: Support other alignment values.
new_fragment_y = m_current_y + (line_box_baseline - fragment.border_box_height());
if (auto length_percentage = vertical_align.get_pointer<CSS::LengthPercentage>(); length_percentage && length_percentage->is_length()) {
auto vertical_align_amount = length_percentage->length().to_px(fragment.layout_node());
new_fragment_y = y_value_for_alignment(CSS::VerticalAlign::Baseline) - vertical_align_amount;
}
}

last_fragment_x_adjustment = new_fragment_x - fragment.offset().x();
Expand All @@ -194,6 +218,7 @@ void LineBuilder::update_last_line()
line_box.m_width += last_fragment_x_adjustment;

line_box.m_bottom = bottom;
line_box.m_baseline = line_box_baseline;
}

void LineBuilder::remove_last_line_if_empty()
Expand Down

0 comments on commit 7dae895

Please sign in to comment.