2022-01-19 11:57:58 +01:00
|
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
|
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
|
2022-01-19 11:57:58 +01:00
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2022-09-11 22:39:01 +02:00
|
|
|
|
#include <LibWeb/Layout/BlockFormattingContext.h>
|
2022-01-19 11:57:58 +01:00
|
|
|
|
#include <LibWeb/Layout/LineBuilder.h>
|
|
|
|
|
#include <LibWeb/Layout/TextNode.h>
|
|
|
|
|
|
|
|
|
|
namespace Web::Layout {
|
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
LineBuilder::LineBuilder(InlineFormattingContext& context, LayoutState& layout_state, LayoutState::UsedValues& containing_block_used_values, CSS::Direction direction, CSS::WritingMode writing_mode)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
: m_context(context)
|
2022-07-16 23:43:48 +02:00
|
|
|
|
, m_layout_state(layout_state)
|
2024-03-15 19:25:00 +01:00
|
|
|
|
, m_containing_block_used_values(containing_block_used_values)
|
2024-08-18 17:58:05 +01:00
|
|
|
|
, m_direction(direction)
|
2024-10-29 11:32:59 +00:00
|
|
|
|
, m_writing_mode(writing_mode)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2024-03-15 19:25:00 +01:00
|
|
|
|
m_text_indent = m_context.containing_block().computed_values().text_indent().to_px(m_context.containing_block(), m_containing_block_used_values.content_width());
|
2022-01-23 01:34:26 +01:00
|
|
|
|
begin_new_line(false);
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-10 15:49:17 +02:00
|
|
|
|
void LineBuilder::break_line(ForcedBreak forced_break, Optional<CSSPixels> next_item_width)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2024-10-29 11:32:59 +00:00
|
|
|
|
// FIXME: Respect inline direction.
|
|
|
|
|
|
2023-06-10 15:49:17 +02:00
|
|
|
|
auto& last_line_box = ensure_last_line_box();
|
2023-03-15 21:11:38 +01:00
|
|
|
|
last_line_box.m_has_break = true;
|
2023-06-10 15:49:17 +02:00
|
|
|
|
last_line_box.m_has_forced_break = forced_break == ForcedBreak::Yes;
|
2023-03-15 21:11:38 +01:00
|
|
|
|
|
2025-08-27 23:59:26 +02:00
|
|
|
|
m_last_line_needs_update = true;
|
2022-01-19 11:57:58 +01:00
|
|
|
|
update_last_line();
|
2025-08-27 23:59:26 +02:00
|
|
|
|
|
2022-09-16 14:21:52 +02:00
|
|
|
|
size_t break_count = 0;
|
|
|
|
|
bool floats_intrude_at_current_y = false;
|
2022-09-11 22:39:01 +02:00
|
|
|
|
do {
|
2024-10-29 11:32:59 +00:00
|
|
|
|
m_containing_block_used_values.line_boxes.append(LineBox(m_direction, m_writing_mode));
|
2022-09-16 14:21:52 +02:00
|
|
|
|
begin_new_line(true, break_count == 0);
|
|
|
|
|
break_count++;
|
2024-10-29 11:32:59 +00:00
|
|
|
|
floats_intrude_at_current_y = m_context.any_floats_intrude_at_block_offset(m_current_block_offset);
|
2025-07-04 12:08:47 +02:00
|
|
|
|
} while (floats_intrude_at_current_y
|
|
|
|
|
&& (!m_context.can_fit_new_line_at_block_offset(m_current_block_offset)
|
|
|
|
|
|| (next_item_width.value_or(0) > m_available_width_for_current_line)));
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-16 14:21:52 +02:00
|
|
|
|
void LineBuilder::begin_new_line(bool increment_y, bool is_first_break_in_sequence)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2022-09-16 14:21:52 +02:00
|
|
|
|
if (increment_y) {
|
|
|
|
|
if (is_first_break_in_sequence) {
|
|
|
|
|
// First break is simple, just go to the start of the next line.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
m_current_block_offset += max(m_max_height_on_current_line, m_context.containing_block().computed_values().line_height());
|
2022-09-16 14:21:52 +02:00
|
|
|
|
} else {
|
|
|
|
|
// We're doing more than one break in a row.
|
|
|
|
|
// This means we're trying to squeeze past intruding floats.
|
|
|
|
|
// Scan 1px at a time until we find a Y value where a new line can fit.
|
|
|
|
|
// FIXME: This is super dumb and inefficient.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
CSSPixels candidate_block_offset = m_current_block_offset + 1;
|
2022-09-16 14:21:52 +02:00
|
|
|
|
while (true) {
|
2024-10-29 11:32:59 +00:00
|
|
|
|
if (m_context.can_fit_new_line_at_block_offset(candidate_block_offset))
|
2022-09-16 14:21:52 +02:00
|
|
|
|
break;
|
2024-10-29 11:32:59 +00:00
|
|
|
|
++candidate_block_offset;
|
2022-09-16 14:21:52 +02:00
|
|
|
|
}
|
2024-10-29 11:32:59 +00:00
|
|
|
|
m_current_block_offset = candidate_block_offset;
|
2022-09-16 14:21:52 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
recalculate_available_space();
|
2023-05-16 09:51:33 +02:00
|
|
|
|
ensure_last_line_box().m_original_available_width = m_available_width_for_current_line;
|
2022-01-19 11:57:58 +01:00
|
|
|
|
m_max_height_on_current_line = 0;
|
2022-01-22 10:32:49 +01:00
|
|
|
|
m_last_line_needs_update = true;
|
2023-05-15 16:42:28 +02:00
|
|
|
|
|
|
|
|
|
// FIXME: Support text-indent with "each-line".
|
2025-04-01 11:01:15 +02:00
|
|
|
|
if (m_containing_block_used_values.line_boxes.size() <= 1)
|
2024-10-29 11:32:59 +00:00
|
|
|
|
ensure_last_line_box().m_inline_length += m_text_indent;
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-20 15:51:24 +01:00
|
|
|
|
LineBox& LineBuilder::ensure_last_line_box()
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2024-03-15 19:25:00 +01:00
|
|
|
|
auto& line_boxes = m_containing_block_used_values.line_boxes;
|
2022-02-20 15:51:24 +01:00
|
|
|
|
if (line_boxes.is_empty())
|
2024-10-29 11:32:59 +00:00
|
|
|
|
line_boxes.append(LineBox(m_direction, m_writing_mode));
|
2022-02-20 15:51:24 +01:00
|
|
|
|
return line_boxes.last();
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
void LineBuilder::append_box(Box const& box, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2022-07-16 23:43:48 +02:00
|
|
|
|
auto& box_state = m_layout_state.get_mutable(box);
|
2022-02-27 10:26:38 +01:00
|
|
|
|
auto& line_box = ensure_last_line_box();
|
2025-09-12 10:06:27 +02:00
|
|
|
|
line_box.add_fragment(box, 0, 0, leading_size, trailing_size, leading_margin, trailing_margin,
|
|
|
|
|
box_state.content_width(), box_state.content_height(), box_state.border_box_top(), box_state.border_box_bottom());
|
2023-03-14 10:28:01 +01:00
|
|
|
|
m_max_height_on_current_line = max(m_max_height_on_current_line, box_state.margin_box_height());
|
2022-02-27 10:26:38 +01:00
|
|
|
|
|
2022-02-28 12:41:23 +01:00
|
|
|
|
box_state.containing_line_box_fragment = LineBoxFragmentCoordinate {
|
2024-03-15 19:25:00 +01:00
|
|
|
|
.line_box_index = m_containing_block_used_values.line_boxes.size() - 1,
|
2022-02-27 10:26:38 +01:00
|
|
|
|
.fragment_index = line_box.fragments().size() - 1,
|
2022-02-28 12:41:23 +01:00
|
|
|
|
};
|
2022-02-20 15:51:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-29 17:14:23 +02:00
|
|
|
|
void LineBuilder::append_text_chunk(TextNode const& text_node, size_t offset_in_node, size_t length_in_node, CSSPixels leading_size, CSSPixels trailing_size, CSSPixels leading_margin, CSSPixels trailing_margin, CSSPixels content_width, CSSPixels content_height, RefPtr<Gfx::GlyphRun> glyph_run)
|
2022-02-20 15:51:24 +01:00
|
|
|
|
{
|
2024-10-29 11:32:59 +00:00
|
|
|
|
auto& line_box = ensure_last_line_box();
|
2025-09-12 10:06:27 +02:00
|
|
|
|
line_box.add_fragment(text_node, offset_in_node, length_in_node, leading_size, trailing_size, leading_margin,
|
|
|
|
|
trailing_margin, content_width, content_height, 0, 0, move(glyph_run));
|
2024-10-29 11:32:59 +00:00
|
|
|
|
|
|
|
|
|
m_max_height_on_current_line = max(m_max_height_on_current_line, line_box.block_length());
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels LineBuilder::y_for_float_to_be_inserted_here(Box const& box)
|
2022-09-16 14:21:52 +02:00
|
|
|
|
{
|
|
|
|
|
auto const& box_state = m_layout_state.get(box);
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels const width = box_state.margin_box_width();
|
|
|
|
|
CSSPixels const height = box_state.margin_box_height();
|
2022-09-22 14:07:28 +02:00
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
CSSPixels candidate_block_offset = m_current_block_offset;
|
2022-09-22 14:07:28 +02:00
|
|
|
|
|
2025-03-26 16:11:50 +00:00
|
|
|
|
// Determine the current line width and subtract trailing whitespace, since those have not yet been removed while
|
|
|
|
|
// placing floating boxes.
|
2023-09-09 15:39:22 +02:00
|
|
|
|
auto const& current_line = ensure_last_line_box();
|
2025-03-26 16:11:50 +00:00
|
|
|
|
auto current_line_width = current_line.width() - current_line.get_trailing_whitespace_width();
|
|
|
|
|
|
2022-09-22 14:07:28 +02:00
|
|
|
|
// If there's already inline content on the current line, check if the new float can fit
|
|
|
|
|
// alongside the content. If not, place it on the next line.
|
2025-03-26 16:11:50 +00:00
|
|
|
|
if (current_line_width > 0 && (current_line_width + width) > m_available_width_for_current_line)
|
2024-10-29 11:32:59 +00:00
|
|
|
|
candidate_block_offset += current_line.height();
|
2022-09-22 14:07:28 +02:00
|
|
|
|
|
|
|
|
|
// Then, look for the next Y position where we can fit the new float.
|
2025-03-26 11:55:39 +00:00
|
|
|
|
auto box_in_root_rect = m_context.parent().content_box_rect_in_ancestor_coordinate_space(box_state, m_context.parent().root());
|
2025-03-27 00:16:02 +00:00
|
|
|
|
|
2025-05-18 19:03:57 +02:00
|
|
|
|
// New floats will always be placed vertically at or below the lowest float.
|
|
|
|
|
// This applies to all floats, so the last inserted float will always be the lowest.
|
|
|
|
|
auto last_float = m_context.parent().last_inserted_float();
|
|
|
|
|
if (last_float.has_value()) {
|
|
|
|
|
auto float_box_top = last_float->margin_box_rect_in_root_coordinate_space.top() - box_in_root_rect.y();
|
|
|
|
|
candidate_block_offset = max(candidate_block_offset, float_box_top);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-27 00:16:02 +00:00
|
|
|
|
HashMap<CSSPixels, AvailableSize> available_space_cache;
|
|
|
|
|
for (;;) {
|
|
|
|
|
Optional<CSSPixels> highest_intersection_bottom;
|
2025-04-01 13:22:22 +02:00
|
|
|
|
auto candidate_block_bottom = candidate_block_offset + height;
|
2025-03-27 00:16:02 +00:00
|
|
|
|
|
|
|
|
|
m_context.parent().for_each_floating_box([&](auto const& float_box) {
|
2025-04-01 13:22:22 +02:00
|
|
|
|
auto float_box_top = float_box.margin_box_rect_in_root_coordinate_space.top() - box_in_root_rect.y();
|
|
|
|
|
auto float_box_bottom = float_box.margin_box_rect_in_root_coordinate_space.bottom() - box_in_root_rect.y();
|
|
|
|
|
if (float_box_bottom <= candidate_block_offset)
|
2025-03-27 00:16:02 +00:00
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
|
|
|
|
|
auto intersection_test = [&](auto y_coordinate, auto top, auto bottom) {
|
|
|
|
|
if (y_coordinate < top || y_coordinate > bottom)
|
|
|
|
|
return;
|
|
|
|
|
auto available_space = available_space_cache.ensure(y_coordinate, [&]() {
|
|
|
|
|
return m_context.available_space_for_line(y_coordinate);
|
|
|
|
|
});
|
2025-04-01 13:22:22 +02:00
|
|
|
|
if (width > available_space)
|
|
|
|
|
highest_intersection_bottom = min(highest_intersection_bottom.value_or(float_box_bottom), float_box_bottom);
|
2025-03-27 00:16:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
2025-04-01 13:22:22 +02:00
|
|
|
|
intersection_test(float_box_top, candidate_block_offset, candidate_block_bottom);
|
|
|
|
|
intersection_test(float_box_bottom, candidate_block_offset, candidate_block_bottom);
|
|
|
|
|
intersection_test(candidate_block_offset, float_box_top, float_box_bottom);
|
|
|
|
|
intersection_test(candidate_block_bottom, float_box_top, float_box_bottom);
|
2025-03-27 00:16:02 +00:00
|
|
|
|
|
2025-03-26 11:55:39 +00:00
|
|
|
|
return IterationDecision::Continue;
|
2025-03-27 00:16:02 +00:00
|
|
|
|
});
|
|
|
|
|
if (!highest_intersection_bottom.has_value() || highest_intersection_bottom.value() == candidate_block_offset)
|
|
|
|
|
break;
|
|
|
|
|
candidate_block_offset = highest_intersection_bottom.value();
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 11:44:23 +02:00
|
|
|
|
return max(candidate_block_offset, m_context.vertical_float_clearance());
|
2022-09-16 14:21:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
bool LineBuilder::should_break(CSSPixels next_item_width)
|
2022-01-19 11:57:58 +01:00
|
|
|
|
{
|
2023-08-12 18:30:18 +02:00
|
|
|
|
if (m_available_width_for_current_line.is_max_content())
|
2022-01-22 10:32:49 +01:00
|
|
|
|
return false;
|
2022-09-11 22:39:01 +02:00
|
|
|
|
|
2024-03-15 19:25:00 +01:00
|
|
|
|
auto const& line_boxes = m_containing_block_used_values.line_boxes;
|
2022-09-11 22:39:01 +02:00
|
|
|
|
if (line_boxes.is_empty() || line_boxes.last().is_empty()) {
|
|
|
|
|
// If we don't have a single line box yet *and* there are no floats intruding
|
|
|
|
|
// at this Y coordinate, we don't need to break before inserting anything.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
if (!m_context.any_floats_intrude_at_block_offset(m_current_block_offset))
|
2022-09-11 22:39:01 +02:00
|
|
|
|
return false;
|
2024-10-29 11:32:59 +00:00
|
|
|
|
if (!m_context.any_floats_intrude_at_block_offset(m_current_block_offset + m_context.containing_block().computed_values().line_height()))
|
2022-09-16 14:21:52 +02:00
|
|
|
|
return false;
|
2022-09-11 22:39:01 +02:00
|
|
|
|
}
|
|
|
|
|
auto current_line_width = ensure_last_line_box().width();
|
2022-09-28 18:35:22 +02:00
|
|
|
|
return (current_line_width + next_item_width) > m_available_width_for_current_line;
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LineBuilder::update_last_line()
|
|
|
|
|
{
|
2025-08-27 23:59:26 +02:00
|
|
|
|
if (!m_last_line_needs_update)
|
|
|
|
|
return;
|
2022-01-22 10:32:49 +01:00
|
|
|
|
m_last_line_needs_update = false;
|
|
|
|
|
|
2025-08-27 23:59:26 +02:00
|
|
|
|
auto& line_boxes = m_containing_block_used_values.line_boxes;
|
2022-02-20 15:51:24 +01:00
|
|
|
|
if (line_boxes.is_empty())
|
2022-01-19 11:57:58 +01:00
|
|
|
|
return;
|
|
|
|
|
|
2022-02-20 15:51:24 +01:00
|
|
|
|
auto& line_box = line_boxes.last();
|
2022-01-19 11:57:58 +01:00
|
|
|
|
|
|
|
|
|
auto text_align = m_context.containing_block().computed_values().text_align();
|
2024-08-11 18:14:12 +01:00
|
|
|
|
auto direction = m_context.containing_block().computed_values().direction();
|
2022-09-16 14:21:52 +02:00
|
|
|
|
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto current_line_height = max(m_max_height_on_current_line, m_context.containing_block().computed_values().line_height());
|
2024-10-29 11:32:59 +00:00
|
|
|
|
CSSPixels inline_offset_top = m_context.leftmost_inline_offset_at(m_current_block_offset);
|
|
|
|
|
CSSPixels inline_offset_bottom = m_context.leftmost_inline_offset_at(m_current_block_offset + current_line_height - 1);
|
|
|
|
|
CSSPixels inline_offset = max(inline_offset_top, inline_offset_bottom);
|
|
|
|
|
CSSPixels block_offset = 0;
|
2022-09-16 14:21:52 +02:00
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
// FIXME: Respect inline direction.
|
|
|
|
|
CSSPixels excess_inline_space = m_available_width_for_current_line.to_px_or_zero() - line_box.inline_length();
|
2022-01-19 11:57:58 +01:00
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
if (m_writing_mode != CSS::WritingMode::HorizontalTb) {
|
|
|
|
|
block_offset = m_available_width_for_current_line.to_px_or_zero() - line_box.block_length();
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-01 20:42:41 -04:00
|
|
|
|
// If (after justification, if any) the inline contents of a line box are too long to fit within it,
|
|
|
|
|
// then the contents are start-aligned: any content that doesn't fit overflows the line box’s end edge.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
if (excess_inline_space > 0) {
|
2023-06-01 20:42:41 -04:00
|
|
|
|
switch (text_align) {
|
|
|
|
|
case CSS::TextAlign::Center:
|
|
|
|
|
case CSS::TextAlign::LibwebCenter:
|
2025-07-14 15:03:37 +02:00
|
|
|
|
case CSS::TextAlign::LibwebInheritOrCenter:
|
2024-10-29 11:32:59 +00:00
|
|
|
|
inline_offset += excess_inline_space / 2;
|
2023-06-01 20:42:41 -04:00
|
|
|
|
break;
|
2024-08-11 18:14:12 +01:00
|
|
|
|
case CSS::TextAlign::Start:
|
|
|
|
|
if (direction == CSS::Direction::Rtl)
|
2024-10-29 11:32:59 +00:00
|
|
|
|
inline_offset += excess_inline_space;
|
2024-08-11 18:14:12 +01:00
|
|
|
|
break;
|
|
|
|
|
case CSS::TextAlign::End:
|
|
|
|
|
if (direction == CSS::Direction::Ltr)
|
2024-10-29 11:32:59 +00:00
|
|
|
|
inline_offset += excess_inline_space;
|
2024-08-11 18:14:12 +01:00
|
|
|
|
break;
|
2023-06-01 20:42:41 -04:00
|
|
|
|
case CSS::TextAlign::Right:
|
2023-06-15 17:31:27 +02:00
|
|
|
|
case CSS::TextAlign::LibwebRight:
|
2024-10-29 11:32:59 +00:00
|
|
|
|
inline_offset += excess_inline_space;
|
2023-06-01 20:42:41 -04:00
|
|
|
|
break;
|
2025-02-03 16:06:07 +00:00
|
|
|
|
case CSS::TextAlign::MatchParent:
|
|
|
|
|
// This should have been replaced before this point.
|
|
|
|
|
VERIFY_NOT_REACHED();
|
2023-06-01 20:42:41 -04:00
|
|
|
|
case CSS::TextAlign::Left:
|
2023-06-15 17:31:27 +02:00
|
|
|
|
case CSS::TextAlign::LibwebLeft:
|
2023-06-01 20:42:41 -04:00
|
|
|
|
case CSS::TextAlign::Justify:
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 18:35:19 +03:00
|
|
|
|
auto strut_baseline = [&] {
|
2023-12-09 23:42:02 +01:00
|
|
|
|
auto& font = m_context.containing_block().first_available_font();
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto const line_height = m_context.containing_block().computed_values().line_height();
|
2022-12-07 18:35:19 +03:00
|
|
|
|
auto const font_metrics = font.pixel_metrics();
|
2023-08-26 15:57:31 +01:00
|
|
|
|
auto const typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
|
2022-12-07 18:35:19 +03:00
|
|
|
|
auto const leading = line_height - typographic_height;
|
|
|
|
|
auto const half_leading = leading / 2;
|
2023-08-26 15:57:31 +01:00
|
|
|
|
return CSSPixels::nearest_value_for(font_metrics.ascent) + half_leading;
|
2022-12-07 18:35:19 +03:00
|
|
|
|
}();
|
|
|
|
|
|
2022-03-03 10:09:10 +01:00
|
|
|
|
auto line_box_baseline = [&] {
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels line_box_baseline = strut_baseline;
|
2022-03-28 21:05:51 +02:00
|
|
|
|
for (auto& fragment : line_box.fragments()) {
|
2023-12-09 23:42:02 +01:00
|
|
|
|
auto const& font = fragment.layout_node().first_available_font();
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto const line_height = fragment.layout_node().computed_values().line_height();
|
2022-03-29 22:33:44 +02:00
|
|
|
|
auto const font_metrics = font.pixel_metrics();
|
2023-08-26 15:57:31 +01:00
|
|
|
|
auto const typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
|
2022-03-29 22:33:44 +02:00
|
|
|
|
auto const leading = line_height - typographic_height;
|
|
|
|
|
auto const half_leading = leading / 2;
|
|
|
|
|
|
|
|
|
|
// The CSS specification calls this AD (A+D, Ascent + Descent).
|
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels fragment_baseline = 0;
|
2022-03-29 22:33:44 +02:00
|
|
|
|
if (fragment.layout_node().is_text_node()) {
|
2023-08-26 15:57:31 +01:00
|
|
|
|
fragment_baseline = CSSPixels::nearest_value_for(font_metrics.ascent) + half_leading;
|
2022-03-29 22:33:44 +02:00
|
|
|
|
} else {
|
2025-01-21 09:12:05 -05:00
|
|
|
|
auto const& box = as<Layout::Box>(fragment.layout_node());
|
2023-05-31 10:18:34 +02:00
|
|
|
|
fragment_baseline = m_context.box_baseline(box);
|
2022-03-29 22:33:44 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remember the baseline used for this fragment. This will be used when painting the fragment.
|
|
|
|
|
fragment.set_baseline(fragment_baseline);
|
|
|
|
|
|
|
|
|
|
// NOTE: For fragments with a <length> vertical-align, shift the line box baseline down by the length.
|
2022-03-03 10:09:10 +01:00
|
|
|
|
// This ensures that we make enough vertical space on the line for any manually-aligned fragments.
|
2023-03-29 13:04:51 +02:00
|
|
|
|
if (auto const* length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>()) {
|
|
|
|
|
if (length_percentage->is_length())
|
|
|
|
|
fragment_baseline += length_percentage->length().to_px(fragment.layout_node());
|
|
|
|
|
else if (length_percentage->is_percentage())
|
2023-08-26 15:03:04 +01:00
|
|
|
|
fragment_baseline += line_height.scaled(length_percentage->percentage().as_fraction());
|
2023-03-29 13:04:51 +02:00
|
|
|
|
}
|
2022-03-28 21:05:51 +02:00
|
|
|
|
|
2022-03-29 22:33:44 +02:00
|
|
|
|
line_box_baseline = max(line_box_baseline, fragment_baseline);
|
2022-02-26 09:29:51 +01:00
|
|
|
|
}
|
2022-03-03 10:09:10 +01:00
|
|
|
|
return line_box_baseline;
|
|
|
|
|
}();
|
2022-01-21 13:19:20 +01:00
|
|
|
|
|
2022-03-24 22:45:51 +01:00
|
|
|
|
// Start with the "strut", an imaginary zero-width box at the start of each line box.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
auto strut_top = m_current_block_offset;
|
|
|
|
|
auto strut_bottom = m_current_block_offset + m_context.containing_block().computed_values().line_height();
|
2022-03-24 22:45:51 +01:00
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels uppermost_box_top = strut_top;
|
|
|
|
|
CSSPixels lowermost_box_bottom = strut_bottom;
|
2022-03-24 22:45:51 +01:00
|
|
|
|
|
2025-09-12 10:06:27 +02:00
|
|
|
|
for (auto& fragment : line_box.fragments()) {
|
2024-12-09 10:24:26 +01:00
|
|
|
|
CSSPixels new_fragment_inline_offset = inline_offset + fragment.inline_offset();
|
2024-10-29 11:32:59 +00:00
|
|
|
|
CSSPixels new_fragment_block_offset = 0;
|
2022-02-26 09:29:51 +01:00
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
auto block_offset_value_for_alignment = [&](CSS::VerticalAlign vertical_align) {
|
2024-08-26 10:28:26 +02:00
|
|
|
|
CSSPixels effective_box_top_offset = fragment.border_box_top();
|
|
|
|
|
CSSPixels effective_box_bottom_offset = fragment.border_box_top();
|
2023-03-14 10:28:01 +01:00
|
|
|
|
if (fragment.is_atomic_inline()) {
|
|
|
|
|
auto const& fragment_box_state = m_layout_state.get(static_cast<Box const&>(fragment.layout_node()));
|
2024-08-26 10:28:26 +02:00
|
|
|
|
effective_box_top_offset = fragment_box_state.margin_box_top();
|
|
|
|
|
effective_box_bottom_offset = fragment_box_state.margin_box_bottom();
|
2023-03-14 10:28:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-02-21 00:46:13 +01:00
|
|
|
|
auto alphabetic_baseline = m_current_block_offset + line_box_baseline - fragment.baseline() + effective_box_top_offset;
|
|
|
|
|
|
2022-03-03 10:09:10 +01:00
|
|
|
|
switch (vertical_align) {
|
|
|
|
|
case CSS::VerticalAlign::Baseline:
|
2025-02-21 00:46:13 +01:00
|
|
|
|
return alphabetic_baseline;
|
2022-03-24 22:56:06 +01:00
|
|
|
|
case CSS::VerticalAlign::Top:
|
2024-10-29 11:32:59 +00:00
|
|
|
|
return m_current_block_offset + effective_box_top_offset;
|
2024-08-26 10:28:26 +02:00
|
|
|
|
case CSS::VerticalAlign::Middle: {
|
|
|
|
|
// Align the vertical midpoint of the box with the baseline of the parent box
|
|
|
|
|
// plus half the x-height of the parent.
|
|
|
|
|
auto const x_height = CSSPixels::nearest_value_for(m_context.containing_block().first_available_font().pixel_metrics().x_height);
|
2024-10-29 11:32:59 +00:00
|
|
|
|
return m_current_block_offset + line_box_baseline + ((effective_box_top_offset - effective_box_bottom_offset - x_height - fragment.height()) / 2);
|
2024-08-26 10:28:26 +02:00
|
|
|
|
}
|
2025-03-23 01:36:17 +00:00
|
|
|
|
case CSS::VerticalAlign::Sub:
|
|
|
|
|
// https://drafts.csswg.org/css-inline/#valdef-baseline-shift-sub
|
|
|
|
|
// Lower by the offset appropriate for subscripts of the parent’s box.
|
|
|
|
|
// The UA may use the parent’s font metrics to find this offset; otherwise it defaults to dropping by one fifth of the parent’s used font-size.
|
|
|
|
|
// FIXME: Use font metrics to find a more appropriate offset, if possible
|
|
|
|
|
return alphabetic_baseline + m_context.containing_block().computed_values().font_size() / 5;
|
2025-03-23 01:34:58 +00:00
|
|
|
|
case CSS::VerticalAlign::Super:
|
|
|
|
|
// https://drafts.csswg.org/css-inline/#valdef-baseline-shift-super
|
|
|
|
|
// Raise by the offset appropriate for superscripts of the parent’s box.
|
|
|
|
|
// The UA may use the parent’s font metrics to find this offset; otherwise it defaults to raising by one third of the parent’s used font-size.
|
|
|
|
|
// FIXME: Use font metrics to find a more appropriate offset, if possible
|
|
|
|
|
return alphabetic_baseline - m_context.containing_block().computed_values().font_size() / 3;
|
2022-03-24 22:56:06 +01:00
|
|
|
|
case CSS::VerticalAlign::Bottom:
|
2022-03-03 10:09:10 +01:00
|
|
|
|
case CSS::VerticalAlign::TextBottom:
|
|
|
|
|
case CSS::VerticalAlign::TextTop:
|
|
|
|
|
// FIXME: These are all 'baseline'
|
2025-02-21 00:46:13 +01:00
|
|
|
|
return alphabetic_baseline;
|
2022-03-03 10:09:10 +01:00
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto const& vertical_align = fragment.layout_node().computed_values().vertical_align();
|
|
|
|
|
if (vertical_align.has<CSS::VerticalAlign>()) {
|
2024-10-29 11:32:59 +00:00
|
|
|
|
new_fragment_block_offset = block_offset_value_for_alignment(vertical_align.get<CSS::VerticalAlign>());
|
2022-02-26 09:29:51 +01:00
|
|
|
|
} else {
|
2023-03-29 13:04:51 +02:00
|
|
|
|
if (auto const* length_percentage = vertical_align.get_pointer<CSS::LengthPercentage>()) {
|
|
|
|
|
if (length_percentage->is_length()) {
|
|
|
|
|
auto vertical_align_amount = length_percentage->length().to_px(fragment.layout_node());
|
2024-10-29 11:32:59 +00:00
|
|
|
|
new_fragment_block_offset = block_offset_value_for_alignment(CSS::VerticalAlign::Baseline) - vertical_align_amount;
|
2023-03-29 13:04:51 +02:00
|
|
|
|
} else if (length_percentage->is_percentage()) {
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto vertical_align_amount = m_context.containing_block().computed_values().line_height().scaled(length_percentage->percentage().as_fraction());
|
2024-10-29 11:32:59 +00:00
|
|
|
|
new_fragment_block_offset = block_offset_value_for_alignment(CSS::VerticalAlign::Baseline) - vertical_align_amount;
|
2023-03-29 13:04:51 +02:00
|
|
|
|
}
|
2022-03-03 10:09:10 +01:00
|
|
|
|
}
|
2022-02-26 09:29:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
fragment.set_inline_offset(new_fragment_inline_offset);
|
|
|
|
|
fragment.set_block_offset(floor(new_fragment_block_offset) + block_offset);
|
2022-03-29 22:33:44 +02:00
|
|
|
|
|
2022-11-04 17:19:11 +00:00
|
|
|
|
CSSPixels top_of_inline_box = 0;
|
|
|
|
|
CSSPixels bottom_of_inline_box = 0;
|
2022-03-29 22:33:44 +02:00
|
|
|
|
{
|
|
|
|
|
// FIXME: Support inline-table elements.
|
2023-03-14 10:28:01 +01:00
|
|
|
|
if (fragment.is_atomic_inline()) {
|
2022-07-16 23:43:48 +02:00
|
|
|
|
auto const& fragment_box_state = m_layout_state.get(static_cast<Box const&>(fragment.layout_node()));
|
2024-10-29 11:32:59 +00:00
|
|
|
|
top_of_inline_box = (fragment.block_offset() - fragment_box_state.margin_box_top());
|
|
|
|
|
bottom_of_inline_box = (fragment.block_offset() + fragment_box_state.content_height() + fragment_box_state.margin_box_bottom());
|
2022-03-29 22:33:44 +02:00
|
|
|
|
} else {
|
2023-12-09 23:42:02 +01:00
|
|
|
|
auto font_metrics = fragment.layout_node().first_available_font().pixel_metrics();
|
2023-08-26 15:57:31 +01:00
|
|
|
|
auto typographic_height = CSSPixels::nearest_value_for(font_metrics.ascent + font_metrics.descent);
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto leading = fragment.layout_node().computed_values().line_height() - typographic_height;
|
2022-03-29 22:33:44 +02:00
|
|
|
|
auto half_leading = leading / 2;
|
2024-10-29 11:32:59 +00:00
|
|
|
|
top_of_inline_box = (fragment.block_offset() + fragment.baseline() - CSSPixels::nearest_value_for(font_metrics.ascent) - half_leading);
|
|
|
|
|
bottom_of_inline_box = (fragment.block_offset() + fragment.baseline() + CSSPixels::nearest_value_for(font_metrics.descent) + half_leading);
|
2022-03-29 22:33:44 +02:00
|
|
|
|
}
|
2023-03-29 13:04:51 +02:00
|
|
|
|
if (auto const* length_percentage = fragment.layout_node().computed_values().vertical_align().get_pointer<CSS::LengthPercentage>()) {
|
|
|
|
|
if (length_percentage->is_length())
|
|
|
|
|
bottom_of_inline_box += length_percentage->length().to_px(fragment.layout_node());
|
|
|
|
|
else if (length_percentage->is_percentage())
|
2024-01-12 15:34:13 +01:00
|
|
|
|
bottom_of_inline_box += m_context.containing_block().computed_values().line_height().scaled(length_percentage->percentage().as_fraction());
|
2023-03-29 13:04:51 +02:00
|
|
|
|
}
|
2022-03-29 22:33:44 +02:00
|
|
|
|
}
|
2022-01-19 11:57:58 +01:00
|
|
|
|
|
2022-03-29 22:33:44 +02:00
|
|
|
|
uppermost_box_top = min(uppermost_box_top, top_of_inline_box);
|
|
|
|
|
lowermost_box_bottom = max(lowermost_box_bottom, bottom_of_inline_box);
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-24 22:45:51 +01:00
|
|
|
|
// 3. The line box height is the distance between the uppermost box top and the lowermost box bottom.
|
2024-10-29 11:32:59 +00:00
|
|
|
|
line_box.m_block_length = lowermost_box_bottom - uppermost_box_top;
|
2022-03-24 22:45:51 +01:00
|
|
|
|
|
2024-10-29 11:32:59 +00:00
|
|
|
|
line_box.m_bottom = m_current_block_offset + line_box.m_block_length;
|
2022-03-03 10:09:10 +01:00
|
|
|
|
line_box.m_baseline = line_box_baseline;
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-22 10:32:49 +01:00
|
|
|
|
void LineBuilder::remove_last_line_if_empty()
|
|
|
|
|
{
|
|
|
|
|
// If there's an empty line box at the bottom, just remove it instead of giving it height.
|
2024-03-15 19:25:00 +01:00
|
|
|
|
auto& line_boxes = m_containing_block_used_values.line_boxes;
|
2023-03-15 21:11:38 +01:00
|
|
|
|
if (!line_boxes.is_empty() && line_boxes.last().is_empty()) {
|
2022-01-22 10:32:49 +01:00
|
|
|
|
line_boxes.take_last();
|
|
|
|
|
m_last_line_needs_update = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-22 19:18:05 +01:00
|
|
|
|
|
2022-09-16 14:21:52 +02:00
|
|
|
|
void LineBuilder::recalculate_available_space()
|
2022-03-22 19:18:05 +01:00
|
|
|
|
{
|
2024-01-12 15:34:13 +01:00
|
|
|
|
auto current_line_height = max(m_max_height_on_current_line, m_context.containing_block().computed_values().line_height());
|
2024-10-29 11:32:59 +00:00
|
|
|
|
auto available_at_top_of_line_box = m_context.available_space_for_line(m_current_block_offset);
|
|
|
|
|
auto available_at_bottom_of_line_box = m_context.available_space_for_line(m_current_block_offset + current_line_height - 1);
|
2022-11-04 17:19:11 +00:00
|
|
|
|
m_available_width_for_current_line = min(available_at_bottom_of_line_box, available_at_top_of_line_box);
|
2024-03-15 19:25:00 +01:00
|
|
|
|
if (!m_containing_block_used_values.line_boxes.is_empty())
|
|
|
|
|
m_containing_block_used_values.line_boxes.last().m_original_available_width = m_available_width_for_current_line;
|
2022-03-22 19:18:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 11:01:15 +02:00
|
|
|
|
void LineBuilder::did_introduce_clearance(CSSPixels clearance)
|
|
|
|
|
{
|
|
|
|
|
// If clearance was introduced but our current line box starts beyond it, we don't need to do anything.
|
|
|
|
|
if (clearance <= m_current_block_offset)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Increase the height of the previous line box so it matches the clearance, because the element's height is first
|
|
|
|
|
// determined by the bottom of the last line box (after trimming empty/whitespace boxes).
|
|
|
|
|
auto& line_boxes = m_containing_block_used_values.line_boxes;
|
|
|
|
|
if (line_boxes.size() > 1) {
|
|
|
|
|
auto& previous_line_box = line_boxes[line_boxes.size() - 2];
|
|
|
|
|
previous_line_box.m_bottom = clearance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The current line box will start directly after any cleared floats.
|
|
|
|
|
m_current_block_offset = clearance;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 11:57:58 +01:00
|
|
|
|
}
|