Repository: LadybirdBrowser/ladybird
File: Libraries/LibWeb/Layout/InlineLevelIterator.cpp (C++)

Code before:
        for (auto const& [key, feature_value] : feature_settings) {
            merged_features.set(key, feature_value.resolved(calculation_context).value_or(0));
        }
    }

    Gfx::ShapeFeatures shape_features;
    shape_features.ensure_capacity(merged_features.size());

    for (auto& it : merged_features) {
        shape_features.unchecked_append({ { it.key[0], it.key[1], it.key[2], it.key[3] }, static_cast<u32>(it.value) });
    }

    return shape_features;
}

Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead()
{
    if (!m_current_node)
        return {};

    if (is<Layout::TextNode>(*m_current_node)) {
        auto& text_node = static_cast<Layout::TextNode const&>(*m_current_node);

        if (!m_text_node_context.has_value())
            enter_text_node(text_node);
        else
            m_text_node_context->is_first_chunk = false;

        auto chunk_opt = m_text_node_context->chunk_iterator.next();
        if (!chunk_opt.has_value()) {
            m_text_node_context = {};
            skip_to_next();
            return next_without_lookahead();
        }

        if (!m_text_node_context->chunk_iterator.peek(0).has_value())
            m_text_node_context->is_last_chunk = true;

        auto& chunk = chunk_opt.value();
        auto text_type = chunk.text_type;
        if (text_type == Gfx::GlyphRun::TextType::Ltr || text_type == Gfx::GlyphRun::TextType::Rtl)
            m_text_node_context->last_known_direction = text_type;

        if (m_text_node_context->do_respect_linebreaks && chunk.has_breaking_newline) {
            m_text_node_context->is_last_chunk = true;
            if (chunk.is_all_whitespace)
                text_type = Gfx::GlyphRun::TextType::EndPadding;
        }

        if (text_type == Gfx::GlyphRun::TextType::ContextDependent)
            text_type = resolve_text_direction_from_context();

Review comment (bug):
Good find! The only place we use this in, is `InlineLevelIterator::add_extra_box_model_metrics_to_item` as the second argument (`bool add_leading_metrics`). Apparently that bool was always true, but we didn't see this pop up as a bug? So either this bool is useless and the behavior was correct through other metrics, or we are missing a test exposing this bug.

Could you check if something's fixed as a result of your change and if so, add a test preventing regressions?

Code after:
        auto const& feature_settings = font_feature_settings.value();
        for (auto const& [key, feature_value] : feature_settings) {
            merged_features.set(key, feature_value.resolved(calculation_context).value_or(0));
        }
    }

    Gfx::ShapeFeatures shape_features;
    shape_features.ensure_capacity(merged_features.size());

    for (auto& it : merged_features) {
        shape_features.unchecked_append({ { it.key[0], it.key[1], it.key[2], it.key[3] }, static_cast<u32>(it.value) });
    }

    return shape_features;
}

Optional<InlineLevelIterator::Item> InlineLevelIterator::next_without_lookahead()
{
    if (!m_current_node)
        return {};

    if (auto* text_node = as_if<Layout::TextNode>(*m_current_node)) {
        if (!m_text_node_context.has_value())
            enter_text_node(*text_node);
        else
            m_text_node_context->is_first_chunk = false;

        if (!m_text_node_context->chunk_iterator.peek(0).has_value())
            m_text_node_context->is_last_chunk = true;

        auto chunk_opt = m_text_node_context->chunk_iterator.next();
        auto is_empty_editable = false;
        if (!chunk_opt.has_value()) {
            auto const is_only_chunk = m_text_node_context->is_first_chunk && m_text_node_context->is_last_chunk;
            if (is_only_chunk && text_node->text_for_rendering().is_empty()) {
                if (auto const* shadow_root = as_if<DOM::ShadowRoot>(text_node->dom_node().root()))
                    if (auto const* form_associated_element = as_if<HTML::FormAssociatedTextControlElement>(shadow_root->host()))
                        is_empty_editable = form_associated_element->is_mutable();
                is_empty_editable |= text_node->dom_node().parent() && text_node->dom_node().parent()->is_editing_host();
            }

            if (is_empty_editable) {
                chunk_opt = m_text_node_context->chunk_iterator.create_empty_chunk();
            } else {
                m_text_node_context = {};
                skip_to_next();
                return next_without_lookahead();
            }
        }

        auto& chunk = chunk_opt.value();