Skip to content

Commit

Permalink
LibGfx: Move FontDatabase internals to SystemFontProvider interface
Browse files Browse the repository at this point in the history
This will be the first step is making better use of system libraries
like fontconfig and CoreText to load system fonts for use by the UI
process and the CSS style computer.
  • Loading branch information
ADKaster committed Oct 14, 2024
1 parent c4f7361 commit 870fe81
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 89 deletions.
15 changes: 11 additions & 4 deletions Ladybird/FontPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
#include "FontPlugin.h"
#include <AK/ByteString.h>
#include <AK/String.h>
#include <AK/TypeCasts.h>
#include <LibCore/Resource.h>
#include <LibCore/StandardPaths.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>

#ifdef USE_FONTCONFIG
# include <fontconfig/fontconfig.h>
#endif

namespace Ladybird {

FontPlugin::FontPlugin(bool is_layout_test_mode)
FontPlugin::FontPlugin(bool is_layout_test_mode, Gfx::SystemFontProvider* font_provider)
: m_is_layout_test_mode(is_layout_test_mode)
{
#ifdef USE_FONTCONFIG
Expand All @@ -28,9 +30,14 @@ FontPlugin::FontPlugin(bool is_layout_test_mode)
}
#endif

// Load anything we can find in the system's font directories
for (auto const& path : Core::StandardPaths::font_directories().release_value_but_fixme_should_propagate_errors())
Gfx::FontDatabase::the().load_all_fonts_from_uri(MUST(String::formatted("file://{}", path)));
if (!font_provider)
font_provider = &static_cast<Gfx::PathFontProvider&>(Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>()));
if (is<Gfx::PathFontProvider>(*font_provider)) {
auto& path_font_provider = static_cast<Gfx::PathFontProvider&>(*font_provider);
// Load anything we can find in the system's font directories
for (auto const& path : Core::StandardPaths::font_directories().release_value_but_fixme_should_propagate_errors())
path_font_provider.load_all_fonts_from_uri(MUST(String::formatted("file://{}", path)));
}

update_generic_fonts();

Expand Down
3 changes: 2 additions & 1 deletion Ladybird/FontPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibWeb/Platform/FontPlugin.h>

namespace Ladybird {

class FontPlugin final : public Web::Platform::FontPlugin {
public:
FontPlugin(bool is_layout_test_mode);
FontPlugin(bool is_layout_test_mode, Gfx::SystemFontProvider* = nullptr);
virtual ~FontPlugin();

virtual Gfx::Font& default_font() override;
Expand Down
9 changes: 5 additions & 4 deletions Ladybird/WebContent/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <LibCore/Resource.h>
#include <LibCore/SystemServerTakeover.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
Expand Down Expand Up @@ -128,11 +129,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Core::Process::wait_for_debugger_and_break();
}

auto& font_provider = static_cast<Gfx::PathFontProvider&>(Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>()));
if (force_fontconfig) {
Gfx::FontDatabase::the().set_force_fontconfig(true);
font_provider.set_name_but_fixme_should_create_custom_system_font_provider("FontConfig"_string);
}

Gfx::FontDatabase::the().load_all_fonts_from_uri("resource://fonts"sv);
font_provider.load_all_fonts_from_uri("resource://fonts"sv);

// Layout test mode implies internals object is exposed and the Skia CPU backend is used
if (is_layout_test_mode) {
Expand Down Expand Up @@ -167,7 +168,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)

Web::HTML::Window::set_internals_object_exposed(expose_internals_object);

Web::Platform::FontPlugin::install(*new Ladybird::FontPlugin(is_layout_test_mode));
Web::Platform::FontPlugin::install(*new Ladybird::FontPlugin(is_layout_test_mode, &font_provider));

TRY(Web::Bindings::initialize_main_thread_vm(Web::HTML::EventLoop::Type::Window));

Expand Down
11 changes: 11 additions & 0 deletions Tests/LibGfx/TestWOFF2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibGfx/Font/WOFF2/Loader.h>
#include <LibTest/TestCase.h>

#define TEST_INPUT(x) ("test-inputs/" x)

namespace {
struct Global {
Global()
{
Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>());
}
} global;
}

TEST_CASE(tolerate_incorrect_sfnt_size)
{
auto file = MUST(Core::MappedFile::map(TEST_INPUT("woff2/incorrect_sfnt_size.woff2"sv)));
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibGfx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(SOURCES
Font/Font.cpp
Font/FontData.cpp
Font/FontDatabase.cpp
Font/PathFontProvider.cpp
Font/ScaledFont.cpp
Font/ScaledFontSkia.cpp
Font/Typeface.cpp
Expand Down
82 changes: 13 additions & 69 deletions Userland/Libraries/LibGfx/Font/FontDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,100 +4,44 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/DeprecatedFlyString.h>
#include <AK/FlyString.h>
#include <AK/LexicalPath.h>
#include <AK/Queue.h>
#include <LibCore/Resource.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/WOFF/Loader.h>

namespace Gfx {

// Key function for SystemFontProvider to emit the vtable here
SystemFontProvider::~SystemFontProvider() = default;

FontDatabase& FontDatabase::the()
{
static FontDatabase s_the;
return s_the;
}

struct FontDatabase::Private {
bool force_fontconfig { false };
HashMap<FlyString, Vector<NonnullRefPtr<Typeface>>, AK::ASCIICaseInsensitiveFlyStringTraits> typeface_by_family;
};

void FontDatabase::set_force_fontconfig(bool force_fontconfig)
{
m_private->force_fontconfig = force_fontconfig;
}

bool FontDatabase::should_force_fontconfig() const
SystemFontProvider& FontDatabase::install_system_font_provider(NonnullOwnPtr<SystemFontProvider> provider)
{
return m_private->force_fontconfig;
VERIFY(!m_system_font_provider);
m_system_font_provider = move(provider);
return *m_system_font_provider;
}

void FontDatabase::load_all_fonts_from_uri(StringView uri)
StringView FontDatabase::system_font_provider_name() const
{
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("FontDatabase::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)) {
// FIXME: What about .otf
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_private->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_private->typeface_by_family.ensure(font->family(), [] {
return Vector<NonnullRefPtr<Typeface>> {};
});
family.append(font);
}
}
return IterationDecision::Continue;
});
VERIFY(m_system_font_provider);
return m_system_font_provider->name();
}

FontDatabase::FontDatabase()
: m_private(make<Private>())
{
}
FontDatabase::FontDatabase() = default;

RefPtr<Gfx::Font> FontDatabase::get(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope)
{
auto it = m_private->typeface_by_family.find(family);
if (it == m_private->typeface_by_family.end())
return nullptr;
for (auto const& typeface : it->value) {
if (typeface->weight() == weight && typeface->width() == width && typeface->slope() == slope)
return typeface->scaled_font(point_size);
}
return nullptr;
return m_system_font_provider->get_font(family, point_size, weight, width, slope);
}

void FontDatabase::for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)> callback)
{
auto it = m_private->typeface_by_family.find(family_name);
if (it == m_private->typeface_by_family.end())
return;
for (auto const& typeface : it->value)
callback(*typeface);
m_system_font_provider->for_each_typeface_with_family_name(family_name, move(callback));
}

}
22 changes: 12 additions & 10 deletions Userland/Libraries/LibGfx/Font/FontDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,38 @@

#pragma once

#include <AK/ByteString.h>
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Font/FontWeight.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Forward.h>

namespace Gfx {

class SystemFontProvider {
public:
virtual ~SystemFontProvider();

virtual StringView name() const = 0;
virtual RefPtr<Gfx::Font> get_font(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope) = 0;
virtual void for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)>) = 0;
};

class FontDatabase {
public:
static FontDatabase& the();
SystemFontProvider& install_system_font_provider(NonnullOwnPtr<SystemFontProvider>);

RefPtr<Gfx::Font> get(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope);

void for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)>);

void load_all_fonts_from_uri(StringView);

void set_force_fontconfig(bool);
[[nodiscard]] bool should_force_fontconfig() const;
[[nodiscard]] StringView system_font_provider_name() const;

private:
FontDatabase();
~FontDatabase() = default;

struct Private;
OwnPtr<Private> m_private;
OwnPtr<SystemFontProvider> m_system_font_provider;
};

}
78 changes: 78 additions & 0 deletions Userland/Libraries/LibGfx/Font/PathFontProvider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2024, Andrew Kaster <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <AK/Format.h>
#include <AK/LexicalPath.h>
#include <LibCore/Resource.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibGfx/Font/ScaledFont.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)) {
// FIXME: What about .otf
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)
{
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->scaled_font(point_size);
}
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);
}
}

}
39 changes: 39 additions & 0 deletions Userland/Libraries/LibGfx/Font/PathFontProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2024, Andrew Kaster <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h>

namespace Gfx {

class PathFontProvider final : public SystemFontProvider {
AK_MAKE_NONCOPYABLE(PathFontProvider);
AK_MAKE_NONMOVABLE(PathFontProvider);

public:
PathFontProvider();
virtual ~PathFontProvider() override;

void set_name_but_fixme_should_create_custom_system_font_provider(String name) { m_name = move(name); }

void load_all_fonts_from_uri(StringView);

virtual RefPtr<Gfx::Font> get_font(FlyString const& family, float point_size, unsigned weight, unsigned width, unsigned slope) override;
virtual void for_each_typeface_with_family_name(FlyString const& family_name, Function<void(Typeface const&)>) override;
virtual StringView name() const override { return m_name.bytes_as_string_view(); }

private:
HashMap<FlyString, Vector<NonnullRefPtr<Typeface>>, AK::ASCIICaseInsensitiveFlyStringTraits> m_typeface_by_family;
String m_name { "Path"_string };
};

}
Loading

0 comments on commit 870fe81

Please sign in to comment.