Files
ladybird/Libraries/LibGfx/Font/PathFontProvider.cpp
Callum Law b55023fad3 LibGfx+LibWeb: Resolve font features per font rather than per element
Previously we would resolve font features
(https://drafts.csswg.org/css-fonts-4/#feature-variation-precedence)
per element, while this works for the current subset of the font feature
resolution algorithm that we support, some as yet unimplemented parts
require us to know whether we are resolving against a CSS @font-face
rule, and if so which one (e.g. applying descriptors from the @font-face
rule, deciding which @font-feature-values rules to apply, etc).

To achieve this we store the data required to resolve font features in a
struct and pass that to `FontComputer` which resolves the font features
and stores them with the computed `Font`.

We no longer need to invalidate the font shaping cache when features
change since the features are defined per font (and therefore won't ever
change).
2026-02-02 14:11:43 +00:00

127 lines
4.9 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/LexicalPath.h>
#include <LibCore/Resource.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibGfx/Font/WOFF/Loader.h>
namespace Gfx {
PathFontProvider::PathFontProvider() = default;
PathFontProvider::~PathFontProvider() = default;
void PathFontProvider::load_all_fonts_from_uri(StringView uri)
{
auto root_or_error = Core::Resource::load_from_uri(uri);
if (root_or_error.is_error()) {
if (root_or_error.error().is_errno() && root_or_error.error().code() == ENOENT) {
return;
}
dbgln("PathFontProvider::load_all_fonts_from_uri('{}'): {}", uri, root_or_error.error());
return;
}
auto root = root_or_error.release_value();
root->for_each_descendant_file([this](Core::Resource const& resource) -> IterationDecision {
auto uri = resource.uri();
auto path = LexicalPath(uri.bytes_as_string_view());
if (path.has_extension(".ttf"sv) || path.has_extension(".ttc"sv) || path.has_extension(".otf"sv)) {
if (auto font_or_error = Typeface::try_load_from_resource(resource); !font_or_error.is_error()) {
auto font = font_or_error.release_value();
auto& family = m_typeface_by_family.ensure(font->family(), [] {
return Vector<NonnullRefPtr<Typeface>> {};
});
family.append(font);
}
} else if (path.has_extension(".woff"sv)) {
if (auto font_or_error = WOFF::try_load_from_resource(resource); !font_or_error.is_error()) {
auto font = font_or_error.release_value();
auto& family = m_typeface_by_family.ensure(font->family(), [] {
return Vector<NonnullRefPtr<Typeface>> {};
});
family.append(font);
}
}
return IterationDecision::Continue;
});
}
RefPtr<Gfx::Font> PathFontProvider::get_font(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope, Optional<FontVariationSettings> const& font_variation_settings, Optional<Gfx::ShapeFeatures> const& shape_features)
{
auto const compute_default_font_variation_settings = [&](unsigned weight, unsigned width) {
FontVariationSettings default_font_variation_settings;
default_font_variation_settings.set_weight(static_cast<float>(weight));
switch (width) {
case FontWidth::UltraCondensed:
default_font_variation_settings.set_width(50);
break;
case FontWidth::ExtraCondensed:
default_font_variation_settings.set_width(62.5);
break;
case FontWidth::Condensed:
default_font_variation_settings.set_width(75);
break;
case FontWidth::SemiCondensed:
default_font_variation_settings.set_width(87.5);
break;
case FontWidth::Normal:
default_font_variation_settings.set_width(100);
break;
case FontWidth::SemiExpanded:
default_font_variation_settings.set_width(112.5);
break;
case FontWidth::Expanded:
default_font_variation_settings.set_width(125);
break;
case FontWidth::ExtraExpanded:
default_font_variation_settings.set_width(150);
break;
case FontWidth::UltraExpanded:
default_font_variation_settings.set_width(200);
break;
default:
VERIFY_NOT_REACHED();
}
return default_font_variation_settings;
};
auto const compute_default_shape_features = [&]() {
// NB: These shape features match those applied when all CSS properties are initial values
Gfx::ShapeFeatures shape_features;
shape_features.append({ { 'c', 'l', 'i', 'g' }, 1 });
shape_features.append({ { 'k', 'e', 'r', 'n' }, 1 });
shape_features.append({ { 'l', 'i', 'g', 'a' }, 1 });
return shape_features;
};
auto it = m_typeface_by_family.find(family);
if (it == m_typeface_by_family.end())
return nullptr;
for (auto const& typeface : it->value) {
if (typeface->weight() == weight && typeface->width() == width && typeface->slope() == slope)
return typeface->font(point_size, font_variation_settings.value_or_lazy_evaluated([&] { return compute_default_font_variation_settings(weight, width); }), shape_features.value_or_lazy_evaluated([&] { return compute_default_shape_features(); }));
}
return nullptr;
}
void PathFontProvider::for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)> callback)
{
auto it = m_typeface_by_family.find(family_name);
if (it == m_typeface_by_family.end())
return;
for (auto const& typeface : it->value) {
callback(*typeface);
}
}
}