ladybird/Libraries/LibWeb/Layout/ListItemMarkerBox.cpp
Callum Law 64ccb9a015 LibWeb: Make @counter-style tree-scoped
This was a pretty straightforward change of storing registered counter
styles on the relevant `StyleScope`s and resolving by following the
process to dereference a global tree-scoped name, the only things of
note are:
 - We only define predefined counter styles (e.g. decimal) on the
   document's scope (since otherwise overrides in outer scopes would
   themselves be overriden).
 - When registering counter styles we don't have the full list of
   extendable styles so we defer fallback to "decimal" for undefined
   styles until `CounterStyle::from_counter_style_definition`.
2026-04-15 11:07:38 +01:00

118 lines
5.1 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/Layout/ListItemMarkerBox.h>
#include <LibWeb/Painting/MarkerPaintable.h>
namespace Web::Layout {
GC_DEFINE_ALLOCATOR(ListItemMarkerBox);
ListItemMarkerBox::ListItemMarkerBox(DOM::Document& document, CSS::ListStyleType style_type, CSS::ListStylePosition style_position, GC::Ref<DOM::Element> list_item_element, GC::Ref<CSS::ComputedProperties> style)
: Box(document, nullptr, move(style))
, m_list_style_type(style_type)
, m_list_style_position(style_position)
, m_list_item_element(list_item_element)
{
}
ListItemMarkerBox::~ListItemMarkerBox() = default;
bool ListItemMarkerBox::counter_style_is_rendered_with_custom_image(RefPtr<CSS::CounterStyle const> const& counter_style)
{
// https://drafts.csswg.org/css-counter-styles-3/#simple-symbolic
// When used in list-style-type, a UA may instead render these styles using a UA-generated image or a UA-chosen font
// instead of rendering the specified character in the elements own font. If using an image, it must look similar
// to the character, and must be sized to attractively fill a 1em by 1em square.
if (!counter_style)
return false;
auto const& counter_style_name = counter_style->name();
return first_is_one_of(counter_style_name, "square"_fly_string, "circle"_fly_string, "disc"_fly_string, "disclosure-closed"_fly_string, "disclosure-open"_fly_string);
}
Optional<String> ListItemMarkerBox::text() const
{
// https://drafts.csswg.org/css-lists-3/#text-markers
auto index = m_list_item_element->ordinal_value();
return m_list_style_type.visit(
[](Empty const&) -> Optional<String> {
// none
// The element has no marker string.
return {};
},
[&](RefPtr<CSS::CounterStyle const> const& counter_style) -> Optional<String> {
// <counter-style>
// Specifies the elements marker string as the value of the list-item counter represented using the
// specified <counter-style>. Specifically, the marker string is the result of generating a counter
// representation of the list-item counter value using the specified <counter-style>, prefixed by the prefix
// of the <counter-style>, and followed by the suffix of the <counter-style>. If the specified
// <counter-style> does not exist, decimal is assumed.
if (counter_style_is_rendered_with_custom_image(counter_style))
return {};
// NB: Fallback to decimal if the counter style does not exist is handled within generate_a_counter_representation()
auto counter_representation = CSS::generate_a_counter_representation(counter_style, DOM::AbstractElement { m_list_item_element }.style_scope(), index);
if (!counter_style)
return MUST(String::formatted("{}. ", counter_representation));
return MUST(String::formatted("{}{}{}", counter_style->prefix(), counter_representation, counter_style->suffix()));
},
[](String const& string) -> Optional<String> {
// <string>
// The elements marker string is the specified <string>.
return string;
});
}
GC::Ptr<Painting::Paintable> ListItemMarkerBox::create_paintable() const
{
return Painting::MarkerPaintable::create(*this);
}
CSSPixels ListItemMarkerBox::relative_size() const
{
VERIFY(!m_list_style_type.has<Empty>());
auto font_size = first_available_font().pixel_size();
auto marker_text = text();
if (marker_text.has_value())
return CSSPixels::nearest_value_for(font_size);
// https://drafts.csswg.org/css-counter-styles-3/#simple-symbolic
// NB: The spec allows us to render some predefined symbol counter styles using a UA-generated image instead of
// text, it instructs us to size these in order to attractively fit within a 1em x 1em square. We mimic Firefox
// and generally use a size of 0.35em, except for disclosure open/closed styles which use a size of 0.5em.
static constexpr float marker_image_size_factor = 0.35f;
static constexpr float disclosure_marker_image_size_factor = 0.5f;
auto const& counter_style = m_list_style_type.get<RefPtr<CSS::CounterStyle const>>();
VERIFY(counter_style);
if (counter_style->name() == "square"_fly_string || counter_style->name() == "circle"_fly_string || counter_style->name() == "disc"_fly_string)
return CSSPixels::nearest_value_for(ceilf(font_size * marker_image_size_factor));
if (counter_style->name() == "disclosure-closed"_fly_string || counter_style->name() == "disclosure-open"_fly_string)
return CSSPixels::nearest_value_for(ceilf(font_size * disclosure_marker_image_size_factor));
VERIFY_NOT_REACHED();
}
void ListItemMarkerBox::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_list_item_element);
}
}