diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 8a9c4c7c83..fb22f7f0d9 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -274,7 +274,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) item.margin_start, item.margin_end, item.width, - text_node.computed_values().font_size()); + text_node.line_height()); break; } } diff --git a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp index fba9839902..795c4dad41 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBoxFragment.cpp @@ -139,49 +139,4 @@ Gfx::FloatRect LineBoxFragment::selection_rect(const Gfx::Font& font) const return {}; } -float LineBoxFragment::height_of_inline_level_box(FormattingState const& state) const -{ - auto height = [&] { - // From "10.8 Line height calculations: the 'line-height' and 'vertical-align' properties" - // https://www.w3.org/TR/CSS22/visudet.html#line-height - - // For replaced elements, inline-block elements, and inline-table elements, this is the height of their margin box. - // FIXME: Support inline-table elements. - if (layout_node().is_replaced_box() || layout_node().is_inline_block()) { - auto const& fragment_box_state = state.get(static_cast(layout_node())); - return fragment_box_state.margin_box_height(); - } - // For inline boxes, this is their 'line-height'. - return layout_node().line_height(); - }(); - if (auto length_percentage = layout_node().computed_values().vertical_align().get_pointer(); length_percentage && length_percentage->is_length()) - height += length_percentage->length().to_px(layout_node()); - return height; -} - -float LineBoxFragment::top_of_inline_level_box(FormattingState const& state) const -{ - // FIXME: Support inline-table elements. - if (layout_node().is_replaced_box() || layout_node().is_inline_block()) { - auto const& fragment_box_state = state.get(static_cast(layout_node())); - return m_offset.y() - fragment_box_state.margin_box_top(); - } - return m_offset.y() - (layout_node().line_height() - layout_node().computed_values().font_size()) / 2; -} - -float LineBoxFragment::bottom_of_inline_level_box(FormattingState const& state) const -{ - auto bottom = [&] { - // FIXME: Support inline-table elements. - if (layout_node().is_replaced_box() || layout_node().is_inline_block()) { - auto const& fragment_box_state = state.get(static_cast(layout_node())); - return m_offset.y() + fragment_box_state.content_height + fragment_box_state.margin_box_bottom(); - } - return m_offset.y() + (layout_node().line_height() - layout_node().computed_values().font_size()) / 2; - }(); - if (auto length_percentage = layout_node().computed_values().vertical_align().get_pointer(); length_percentage && length_percentage->is_length()) - bottom += length_percentage->length().to_px(layout_node()); - return bottom; -} - } diff --git a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp index d52c03cac5..4de71b95c0 100644 --- a/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/LineBuilder.cpp @@ -146,32 +146,37 @@ void LineBuilder::update_last_line() break; } - auto fragment_baseline = [&](auto const& fragment) -> float { - float fragment_baseline = 0; - if (fragment.layout_node().is_text_node()) { - fragment_baseline = fragment.layout_node().font().baseline(); - } else { - auto const& box = verify_cast(fragment.layout_node()); - fragment_baseline = box_baseline(m_formatting_state, box); - } - return fragment_baseline; - }; - auto line_box_baseline = [&] { float line_box_baseline = 0; for (auto& fragment : line_box.fragments()) { - auto baseline = fragment_baseline(fragment); - if (fragment.height() < m_context.containing_block().line_height()) - baseline += (m_context.containing_block().line_height() - fragment.height()) / 2; - // NOTE: For fragments with a vertical-align, shift the fragment baseline down by the length. + auto const& font = fragment.layout_node().font(); + auto const line_height = fragment.layout_node().line_height(); + auto const font_metrics = font.pixel_metrics(); + auto const typographic_height = font_metrics.ascent + font_metrics.descent; + auto const leading = line_height - typographic_height; + auto const half_leading = leading / 2; + + // The CSS specification calls this AD (A+D, Ascent + Descent). + + float fragment_baseline = 0; + if (fragment.layout_node().is_text_node()) { + fragment_baseline = font_metrics.ascent; + } else { + auto const& box = verify_cast(fragment.layout_node()); + fragment_baseline = box_baseline(m_formatting_state, box); + } + + fragment_baseline += half_leading; + + // 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 vertical-align, shift the line box 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().template get_pointer(); length_percentage && length_percentage->is_length()) - baseline += length_percentage->length().to_px(fragment.layout_node()); + fragment_baseline += length_percentage->length().to_px(fragment.layout_node()); - // Store the baseline on the fragment. This is used when painting. - fragment.set_baseline(baseline); - - line_box_baseline = max(line_box_baseline, baseline); + line_box_baseline = max(line_box_baseline, fragment_baseline); } return line_box_baseline; }(); @@ -192,7 +197,7 @@ void LineBuilder::update_last_line() auto y_value_for_alignment = [&](CSS::VerticalAlign vertical_align) { switch (vertical_align) { case CSS::VerticalAlign::Baseline: - return m_current_y + line_box_baseline - fragment_baseline(fragment) + fragment.border_box_top(); + return m_current_y + line_box_baseline - fragment.baseline() + fragment.border_box_top(); case CSS::VerticalAlign::Top: return m_current_y + fragment.border_box_top(); case CSS::VerticalAlign::Middle: @@ -202,7 +207,7 @@ void LineBuilder::update_last_line() case CSS::VerticalAlign::TextBottom: case CSS::VerticalAlign::TextTop: // FIXME: These are all 'baseline' - return m_current_y + line_box_baseline - fragment_baseline(fragment) + fragment.border_box_top(); + return m_current_y + line_box_baseline - fragment.baseline() + fragment.border_box_top(); } VERIFY_NOT_REACHED(); }; @@ -217,14 +222,34 @@ void LineBuilder::update_last_line() } } - fragment.set_offset({ new_fragment_x, new_fragment_y }); + fragment.set_offset({ new_fragment_x, floorf(new_fragment_y) }); - uppermost_box_top = min(uppermost_box_top, fragment.top_of_inline_level_box(m_formatting_state)); - lowermost_box_bottom = max(lowermost_box_bottom, fragment.bottom_of_inline_level_box(m_formatting_state)); + float top_of_inline_box = 0; + float bottom_of_inline_box = 0; + { + // FIXME: Support inline-table elements. + if (fragment.layout_node().is_replaced_box() || fragment.layout_node().is_inline_block()) { + auto const& fragment_box_state = m_formatting_state.get(static_cast(fragment.layout_node())); + top_of_inline_box = fragment.offset().y() - fragment_box_state.margin_box_top(); + bottom_of_inline_box = fragment.offset().y() + fragment_box_state.content_height + fragment_box_state.margin_box_bottom(); + } else { + auto font_metrics = fragment.layout_node().font().pixel_metrics(); + auto typographic_height = font_metrics.ascent + font_metrics.descent; + auto leading = fragment.layout_node().line_height() - typographic_height; + auto half_leading = leading / 2; + top_of_inline_box = fragment.offset().y() + fragment.baseline() - font_metrics.ascent - half_leading; + bottom_of_inline_box = fragment.offset().y() + fragment.baseline() + font_metrics.descent + half_leading; + } + if (auto length_percentage = fragment.layout_node().computed_values().vertical_align().template get_pointer(); length_percentage && length_percentage->is_length()) + bottom_of_inline_box += length_percentage->length().to_px(fragment.layout_node()); + } + + uppermost_box_top = min(uppermost_box_top, top_of_inline_box); + lowermost_box_bottom = max(lowermost_box_bottom, bottom_of_inline_box); } // 3. The line box height is the distance between the uppermost box top and the lowermost box bottom. - line_box.m_height = max(m_context.containing_block().line_height(), lowermost_box_bottom - uppermost_box_top); + line_box.m_height = lowermost_box_bottom - uppermost_box_top; line_box.m_bottom = m_current_y + line_box.m_height; line_box.m_baseline = line_box_baseline; diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 2225cbf553..12bfabb273 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -451,8 +451,13 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const 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.absolute_rect()), Color::Green); + if (context.should_show_line_box_borders()) { + auto fragment_absolute_rect = fragment.absolute_rect(); + context.painter().draw_rect(enclosing_int_rect(fragment_absolute_rect), Color::Green); + context.painter().draw_line( + fragment_absolute_rect.top_left().translated(0, fragment.baseline()).to_rounded(), + fragment_absolute_rect.top_right().translated(0, fragment.baseline()).to_rounded(), Color::Red); + } if (is(fragment.layout_node())) paint_text_fragment(context, static_cast(fragment.layout_node()), fragment, phase); }