mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-18 15:52:21 +00:00
We now apply first letter styles by splitting text with a first-letter style applied into 2 `TextSliceNode` objects. The `DOM::Text` layout node always points at the non first-letter slice and the first-letter slice is reachable via `TextSliceNode::first_letter_slice()`. First letter splitting works by `TreeBuilder` walking a block container's inline descendants to find the first typographic letter unit per the pattern given in css-pseudo level 4, which is then wrapped in an anonymous inline box styled with the `::first-letter` computed properties. Consumers that map between DOM offsets and layout geometry are updated to visit all slices of a `DOM::Text` through `TextOffsetMapping`.
75 lines
2.6 KiB
C++
75 lines
2.6 KiB
C++
/*
|
|
* Copyright (c) 2026, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <LibWeb/Forward.h>
|
|
#include <LibWeb/Layout/TextNode.h>
|
|
#include <LibWeb/Painting/Paintable.h>
|
|
#include <LibWeb/Painting/PaintableFragment.h>
|
|
#include <LibWeb/Painting/PaintableWithLines.h>
|
|
|
|
namespace Web::Layout {
|
|
|
|
class TextOffsetMapping {
|
|
public:
|
|
explicit TextOffsetMapping(DOM::Text const&);
|
|
|
|
bool is_split() const { return m_first_letter_slice != nullptr; }
|
|
TextNode const* primary() const { return m_primary; }
|
|
TextSliceNode const* first_letter_slice() const { return m_first_letter_slice; }
|
|
|
|
template<typename Callback>
|
|
void for_each_fragment(Callback&& callback) const
|
|
{
|
|
if (m_first_letter_slice)
|
|
callback(static_cast<TextNode const&>(*m_first_letter_slice));
|
|
if (m_primary)
|
|
callback(*m_primary);
|
|
}
|
|
|
|
template<typename Callback>
|
|
void for_each_fragment(Callback&& callback)
|
|
{
|
|
if (m_first_letter_slice)
|
|
callback(const_cast<TextNode&>(static_cast<TextNode const&>(*m_first_letter_slice)));
|
|
if (m_primary)
|
|
callback(const_cast<TextNode&>(*m_primary));
|
|
}
|
|
|
|
TextNode const* fragment_containing(size_t dom_offset) const;
|
|
|
|
template<typename Callback>
|
|
void for_each_paintable_fragment_in_dom_range(size_t dom_start, size_t dom_end, Callback&& callback) const
|
|
{
|
|
for_each_fragment([&](TextNode const& fragment) {
|
|
auto fragment_paintable = fragment.first_paintable();
|
|
if (!fragment_paintable)
|
|
return;
|
|
|
|
auto paintable_with_lines = fragment_paintable->template first_ancestor_of_type<Painting::PaintableWithLines>();
|
|
if (!paintable_with_lines)
|
|
return;
|
|
for (auto const& paintable_fragment : paintable_with_lines->fragments()) {
|
|
if (&paintable_fragment.paintable() != fragment_paintable)
|
|
continue;
|
|
auto const fragment_dom_start = paintable_fragment.dom_start_offset_in_node();
|
|
auto const fragment_dom_end = paintable_fragment.dom_end_offset_in_node();
|
|
if (fragment_dom_end <= dom_start || fragment_dom_start >= dom_end)
|
|
continue;
|
|
callback(paintable_fragment);
|
|
}
|
|
});
|
|
}
|
|
|
|
private:
|
|
// TextOffsetMapping is a short-lived stack object, and the layout nodes are kept alive by the document's layout
|
|
// tree for the duration of its use, so there's no need to visit these.
|
|
GC::RawPtr<TextNode const> m_primary;
|
|
GC::RawPtr<TextSliceNode const> m_first_letter_slice;
|
|
};
|
|
|
|
}
|