Files
ladybird/Libraries/LibGfx/Font/FontDatabase.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

148 lines
4.8 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2026, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/FlyString.h>
#include <LibCore/StandardPaths.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/TypefaceSkia.h>
#if defined(AK_OS_HAIKU)
# include <FindDirectory.h>
#endif
#ifdef USE_FONTCONFIG
# include <LibGfx/Font/GlobalFontConfig.h>
#endif
namespace Gfx {
// Key function for SystemFontProvider to emit the vtable here
SystemFontProvider::~SystemFontProvider() = default;
FontDatabase& FontDatabase::the()
{
static FontDatabase s_the;
return s_the;
}
SystemFontProvider& FontDatabase::install_system_font_provider(NonnullOwnPtr<SystemFontProvider> provider)
{
VERIFY(!m_system_font_provider);
m_system_font_provider = move(provider);
return *m_system_font_provider;
}
StringView FontDatabase::system_font_provider_name() const
{
VERIFY(m_system_font_provider);
return m_system_font_provider->name();
}
FontDatabase::FontDatabase() = default;
RefPtr<Gfx::Font> FontDatabase::get(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope, Optional<FontVariationSettings> const& font_variation_settings, Optional<Gfx::ShapeFeatures> const& shape_features)
{
return m_system_font_provider->get_font(family, point_size, weight, width, slope, font_variation_settings, shape_features);
}
RefPtr<Gfx::Font> FontDatabase::get_font_for_code_point(u32 code_point, float point_size, u16 weight, u16 width, u8 slope)
{
CodePointFallbackKey key { code_point, weight, width, slope };
auto& entry = m_code_point_fallback_cache.ensure(key, [&]() -> CodePointFallbackEntry {
auto typeface_or_error = TypefaceSkia::find_typeface_for_code_point(code_point, weight, width, slope);
if (typeface_or_error.is_error() || !typeface_or_error.value())
return { {}, nullptr };
auto typeface = typeface_or_error.release_value();
return { typeface->family(), typeface };
});
// FIXME: Does it matter that we don't pass a FontVariationSettings or ShapeFeatures here?
if (entry.typeface)
return entry.typeface->font(point_size, {});
return nullptr;
}
void FontDatabase::for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)> callback)
{
m_system_font_provider->for_each_typeface_with_family_name(family_name, move(callback));
}
ErrorOr<Vector<String>> FontDatabase::font_directories()
{
#if defined(USE_FONTCONFIG)
Vector<String> paths;
FcConfig* config = Gfx::GlobalFontConfig::the().get();
FcStrList* dirs = FcConfigGetFontDirs(config);
while (FcChar8* dir = FcStrListNext(dirs)) {
char const* dir_cstring = reinterpret_cast<char const*>(dir);
paths.append(TRY(String::from_utf8(StringView { dir_cstring, strlen(dir_cstring) })));
}
FcStrListDone(dirs);
return paths;
#elif defined(AK_OS_HAIKU)
Vector<String> paths_vector;
char** paths;
size_t paths_count;
if (find_paths(B_FIND_PATH_FONTS_DIRECTORY, NULL, &paths, &paths_count) == B_OK) {
for (size_t i = 0; i < paths_count; ++i) {
StringBuilder builder;
builder.append(paths[i], strlen(paths[i]));
paths_vector.append(TRY(builder.to_string()));
}
}
return paths_vector;
#else
# if defined(AK_OS_SERENITY)
return Vector<String> { {
"/res/fonts"_string,
} };
# elif defined(AK_OS_MACOS)
return Vector<String> { {
"/System/Library/Fonts"_string,
"/Library/Fonts"_string,
TRY(String::formatted("{}/Library/Fonts"sv, Core::StandardPaths::home_directory())),
} };
# elif defined(AK_OS_ANDROID)
return Vector<String> { {
// FIXME: We should be using the ASystemFontIterator NDK API here.
// There is no guarantee that this will continue to exist on future versions of Android.
"/system/fonts"_string,
} };
# elif defined(AK_OS_WINDOWS)
return Vector<String> { {
TRY(String::formatted(R"({}\Fonts)"sv, getenv("WINDIR"))),
TRY(String::formatted(R"({}\Microsoft\Windows\Fonts)"sv, getenv("LOCALAPPDATA"))),
} };
# else
Vector<String> paths;
auto user_data_directory = Core::StandardPaths::user_data_directory();
paths.append(TRY(String::formatted("{}/fonts", user_data_directory)));
paths.append(TRY(String::formatted("{}/X11/fonts", user_data_directory)));
auto data_directories = Core::StandardPaths::system_data_directories();
for (auto& data_directory : data_directories) {
paths.append(TRY(String::formatted("{}/fonts", data_directory)));
paths.append(TRY(String::formatted("{}/X11/fonts", data_directory)));
}
return paths;
# endif
#endif
}
}