mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
Font computation and loading is distinct enough from style computation that it makes more sense to have this in it's own class. This will be useful later when we move the font loading process to `ComputedProperties` in order to respect animated values.
573 lines
23 KiB
C++
573 lines
23 KiB
C++
/*
|
||
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
||
* Copyright (c) 2021, the SerenityOS developers.
|
||
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
|
||
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
|
||
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include "FontComputer.h"
|
||
#include <AK/NonnullRawPtr.h>
|
||
#include <LibGfx/Font/FontDatabase.h>
|
||
#include <LibGfx/Font/WOFF/Loader.h>
|
||
#include <LibGfx/Font/WOFF2/Loader.h>
|
||
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
||
#include <LibWeb/CSS/ComputedProperties.h>
|
||
#include <LibWeb/CSS/Fetch.h>
|
||
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
|
||
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
|
||
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
|
||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||
#include <LibWeb/DOM/Document.h>
|
||
#include <LibWeb/Fetch/Infrastructure/HTTP/MIME.h>
|
||
#include <LibWeb/Fetch/Response.h>
|
||
#include <LibWeb/MimeSniff/Resource.h>
|
||
#include <LibWeb/Platform/FontPlugin.h>
|
||
|
||
namespace Web::CSS {
|
||
|
||
GC_DEFINE_ALLOCATOR(FontComputer);
|
||
GC_DEFINE_ALLOCATOR(FontLoader);
|
||
|
||
struct FontFaceKey {
|
||
NonnullRawPtr<FlyString const> family_name;
|
||
int weight { 0 };
|
||
int slope { 0 };
|
||
};
|
||
|
||
}
|
||
|
||
namespace AK {
|
||
|
||
namespace Detail {
|
||
|
||
template<>
|
||
inline constexpr bool IsHashCompatible<Web::CSS::FontFaceKey, Web::CSS::OwnFontFaceKey> = true;
|
||
template<>
|
||
inline constexpr bool IsHashCompatible<Web::CSS::OwnFontFaceKey, Web::CSS::FontFaceKey> = true;
|
||
|
||
}
|
||
|
||
template<>
|
||
struct Traits<Web::CSS::FontFaceKey> : public DefaultTraits<Web::CSS::FontFaceKey> {
|
||
static unsigned hash(Web::CSS::FontFaceKey const& key) { return pair_int_hash(key.family_name->hash(), pair_int_hash(key.weight, key.slope)); }
|
||
};
|
||
|
||
template<>
|
||
struct Traits<Web::CSS::OwnFontFaceKey> : public DefaultTraits<Web::CSS::OwnFontFaceKey> {
|
||
static unsigned hash(Web::CSS::OwnFontFaceKey const& key) { return pair_int_hash(key.family_name.hash(), pair_int_hash(key.weight, key.slope)); }
|
||
};
|
||
|
||
template<>
|
||
struct Traits<Web::CSS::FontMatchingAlgorithmCacheKey> : public DefaultTraits<Web::CSS::FontMatchingAlgorithmCacheKey> {
|
||
static unsigned hash(Web::CSS::FontMatchingAlgorithmCacheKey const& key)
|
||
{
|
||
auto hash = key.family_name.hash();
|
||
hash = pair_int_hash(hash, key.weight);
|
||
hash = pair_int_hash(hash, key.slope);
|
||
hash = pair_int_hash(hash, Traits<float>::hash(key.font_size_in_pt));
|
||
return hash;
|
||
}
|
||
};
|
||
|
||
}
|
||
|
||
namespace Web::CSS {
|
||
|
||
OwnFontFaceKey::OwnFontFaceKey(FontFaceKey const& other)
|
||
: family_name(other.family_name)
|
||
, weight(other.weight)
|
||
, slope(other.slope)
|
||
{
|
||
}
|
||
|
||
OwnFontFaceKey::operator FontFaceKey() const
|
||
{
|
||
return FontFaceKey {
|
||
family_name,
|
||
weight,
|
||
slope
|
||
};
|
||
}
|
||
|
||
[[nodiscard]] bool OwnFontFaceKey::operator==(FontFaceKey const& other) const
|
||
{
|
||
return family_name == other.family_name
|
||
&& weight == other.weight
|
||
&& slope == other.slope;
|
||
}
|
||
|
||
FontLoader::FontLoader(FontComputer& font_computer, GC::Ptr<CSSStyleSheet> parent_style_sheet, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL> urls, Function<void(RefPtr<Gfx::Typeface const>)> on_load)
|
||
: m_font_computer(font_computer)
|
||
, m_parent_style_sheet(parent_style_sheet)
|
||
, m_family_name(move(family_name))
|
||
, m_unicode_ranges(move(unicode_ranges))
|
||
, m_urls(move(urls))
|
||
, m_on_load(move(on_load))
|
||
{
|
||
}
|
||
|
||
FontLoader::~FontLoader() = default;
|
||
|
||
void FontLoader::visit_edges(Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
visitor.visit(m_font_computer);
|
||
visitor.visit(m_parent_style_sheet);
|
||
visitor.visit(m_fetch_controller);
|
||
}
|
||
|
||
bool FontLoader::is_loading() const
|
||
{
|
||
return m_fetch_controller && !m_vector_font;
|
||
}
|
||
|
||
RefPtr<Gfx::Font const> FontLoader::font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations)
|
||
{
|
||
if (!m_vector_font) {
|
||
if (!m_fetch_controller)
|
||
start_loading_next_url();
|
||
return nullptr;
|
||
}
|
||
return m_vector_font->font(point_size, variations);
|
||
}
|
||
|
||
void FontLoader::start_loading_next_url()
|
||
{
|
||
// FIXME: Load local() fonts somehow.
|
||
if (m_fetch_controller && m_fetch_controller->state() == Fetch::Infrastructure::FetchController::State::Ongoing)
|
||
return;
|
||
if (m_urls.is_empty())
|
||
return;
|
||
|
||
// https://drafts.csswg.org/css-fonts-4/#fetch-a-font
|
||
// To fetch a font given a selected <url> url for @font-face rule, fetch url, with stylesheet being rule’s parent
|
||
// CSS style sheet, destination "font", CORS mode "cors", and processResponse being the following steps given
|
||
// response res and null, failure or a byte stream stream:
|
||
auto style_sheet_or_document = m_parent_style_sheet ? StyleSheetOrDocument { *m_parent_style_sheet } : StyleSheetOrDocument { m_font_computer->document() };
|
||
m_fetch_controller = fetch_a_style_resource(m_urls.take_first(), style_sheet_or_document, Fetch::Infrastructure::Request::Destination::Font, CorsMode::Cors,
|
||
[loader = this](auto response, auto stream) {
|
||
// 1. If stream is null, return.
|
||
// 2. Load a font from stream according to its type.
|
||
|
||
// NB: We need to fetch the next source if this one fails to fetch OR decode. So, first try to decode it.
|
||
RefPtr<Gfx::Typeface const> typeface;
|
||
if (auto* bytes = stream.template get_pointer<ByteBuffer>()) {
|
||
if (auto maybe_typeface = loader->try_load_font(response, *bytes); !maybe_typeface.is_error())
|
||
typeface = maybe_typeface.release_value();
|
||
}
|
||
|
||
if (!typeface) {
|
||
// NB: If we have other sources available, try the next one.
|
||
if (loader->m_urls.is_empty()) {
|
||
loader->font_did_load_or_fail(nullptr);
|
||
} else {
|
||
loader->m_fetch_controller = nullptr;
|
||
loader->start_loading_next_url();
|
||
}
|
||
} else {
|
||
loader->font_did_load_or_fail(move(typeface));
|
||
}
|
||
});
|
||
|
||
if (!m_fetch_controller)
|
||
font_did_load_or_fail(nullptr);
|
||
}
|
||
|
||
void FontLoader::font_did_load_or_fail(RefPtr<Gfx::Typeface const> typeface)
|
||
{
|
||
if (typeface) {
|
||
m_vector_font = typeface.release_nonnull();
|
||
m_font_computer->did_load_font(m_family_name);
|
||
if (m_on_load)
|
||
m_on_load(m_vector_font);
|
||
} else {
|
||
if (m_on_load)
|
||
m_on_load(nullptr);
|
||
}
|
||
m_fetch_controller = nullptr;
|
||
}
|
||
|
||
ErrorOr<NonnullRefPtr<Gfx::Typeface const>> FontLoader::try_load_font(Fetch::Infrastructure::Response const& response, ByteBuffer const& bytes)
|
||
{
|
||
// FIXME: This could maybe use the format() provided in @font-face as well, since often the mime type is just application/octet-stream and we have to try every format
|
||
auto mime_type = Fetch::Infrastructure::extract_mime_type(response.header_list());
|
||
if (!mime_type.has_value() || !mime_type->is_font()) {
|
||
mime_type = MimeSniff::Resource::sniff(bytes, MimeSniff::SniffingConfiguration { .sniffing_context = MimeSniff::SniffingContext::Font });
|
||
}
|
||
if (mime_type.has_value()) {
|
||
if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv || mime_type->essence() == "font/otf"sv) {
|
||
if (auto result = Gfx::Typeface::try_load_from_temporary_memory(bytes); !result.is_error()) {
|
||
return result;
|
||
}
|
||
}
|
||
if (mime_type->essence() == "font/woff"sv || mime_type->essence() == "application/font-woff"sv) {
|
||
if (auto result = WOFF::try_load_from_bytes(bytes); !result.is_error()) {
|
||
return result;
|
||
}
|
||
}
|
||
if (mime_type->essence() == "font/woff2"sv || mime_type->essence() == "application/font-woff2"sv) {
|
||
if (auto result = WOFF2::try_load_from_bytes(bytes); !result.is_error()) {
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
return Error::from_string_literal("Automatic format detection failed");
|
||
}
|
||
|
||
struct FontComputer::MatchingFontCandidate {
|
||
FontFaceKey key;
|
||
Variant<FontLoaderList*, Gfx::Typeface const*> loader_or_typeface;
|
||
|
||
[[nodiscard]] RefPtr<Gfx::FontCascadeList const> font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations) const
|
||
{
|
||
auto font_list = Gfx::FontCascadeList::create();
|
||
if (auto const* loader_list = loader_or_typeface.get_pointer<FontLoaderList*>(); loader_list) {
|
||
for (auto const& loader : **loader_list) {
|
||
if (auto font = loader->font_with_point_size(point_size, variations); font)
|
||
font_list->add(*font, loader->unicode_ranges());
|
||
}
|
||
return font_list;
|
||
}
|
||
|
||
font_list->add(loader_or_typeface.get<Gfx::Typeface const*>()->font(point_size, variations));
|
||
return font_list;
|
||
}
|
||
};
|
||
|
||
void FontComputer::visit_edges(Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
visitor.visit(m_document);
|
||
visitor.visit(m_loaded_fonts);
|
||
}
|
||
|
||
RefPtr<Gfx::FontCascadeList const> FontComputer::find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive)
|
||
{
|
||
using Fn = AK::Function<bool(MatchingFontCandidate const&)>;
|
||
auto pred = inclusive ? Fn([&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight >= target_weight; })
|
||
: Fn([&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight > target_weight; });
|
||
auto it = find_if(candidates.begin(), candidates.end(), pred);
|
||
for (; it != candidates.end(); ++it) {
|
||
if (auto found_font = it->font_with_point_size(font_size_in_pt, variations))
|
||
return found_font;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
RefPtr<Gfx::FontCascadeList const> FontComputer::find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive)
|
||
{
|
||
using Fn = AK::Function<bool(MatchingFontCandidate const&)>;
|
||
auto pred = inclusive ? Fn([&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight <= target_weight; })
|
||
: Fn([&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight < target_weight; });
|
||
auto it = find_if(candidates.rbegin(), candidates.rend(), pred);
|
||
for (; it != candidates.rend(); ++it) {
|
||
if (auto found_font = it->font_with_point_size(font_size_in_pt, variations))
|
||
return found_font;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
RefPtr<Gfx::FontCascadeList const> FontComputer::font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const
|
||
{
|
||
FontMatchingAlgorithmCacheKey key { family_name, weight, slope, font_size_in_pt };
|
||
return m_font_matching_algorithm_cache.ensure(key, [&] {
|
||
return font_matching_algorithm_impl(family_name, weight, slope, font_size_in_pt);
|
||
});
|
||
}
|
||
|
||
// Partial implementation of the font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
|
||
// FIXME: This should be replaced by the full CSS font selection algorithm.
|
||
RefPtr<Gfx::FontCascadeList const> FontComputer::font_matching_algorithm_impl(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const
|
||
{
|
||
// If a font family match occurs, the user agent assembles the set of font faces in that family and then
|
||
// narrows the set to a single face using other font properties in the order given below.
|
||
Vector<MatchingFontCandidate> matching_family_fonts;
|
||
for (auto const& font_key_and_loader : m_loaded_fonts) {
|
||
if (font_key_and_loader.key.family_name.equals_ignoring_ascii_case(family_name))
|
||
matching_family_fonts.empend(font_key_and_loader.key, const_cast<FontLoaderList*>(&font_key_and_loader.value));
|
||
}
|
||
Gfx::FontDatabase::the().for_each_typeface_with_family_name(family_name, [&](Gfx::Typeface const& typeface) {
|
||
matching_family_fonts.empend(
|
||
FontFaceKey {
|
||
.family_name = typeface.family(),
|
||
.weight = static_cast<int>(typeface.weight()),
|
||
.slope = typeface.slope(),
|
||
},
|
||
&typeface);
|
||
});
|
||
quick_sort(matching_family_fonts, [](auto const& a, auto const& b) {
|
||
return a.key.weight < b.key.weight;
|
||
});
|
||
// FIXME: 1. font-stretch is tried first.
|
||
// FIXME: 2. font-style is tried next.
|
||
// We don't have complete support of italic and oblique fonts, so matching on font-style can be simplified to:
|
||
// If a matching slope is found, all faces which don't have that matching slope are excluded from the matching set.
|
||
auto style_it = find_if(matching_family_fonts.begin(), matching_family_fonts.end(),
|
||
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.slope == slope; });
|
||
if (style_it != matching_family_fonts.end()) {
|
||
matching_family_fonts.remove_all_matching([&](auto const& matching_font_candidate) {
|
||
return matching_font_candidate.key.slope != slope;
|
||
});
|
||
}
|
||
// 3. font-weight is matched next.
|
||
// If the desired weight is inclusively between 400 and 500, weights greater than or equal to the target weight
|
||
// are checked in ascending order until 500 is hit and checked, followed by weights less than the target weight
|
||
// in descending order, followed by weights greater than 500, until a match is found.
|
||
|
||
Gfx::FontVariationSettings variations;
|
||
variations.set_weight(weight);
|
||
|
||
if (weight >= 400 && weight <= 500) {
|
||
auto it = find_if(matching_family_fonts.begin(), matching_family_fonts.end(),
|
||
[&](auto const& matching_font_candidate) { return matching_font_candidate.key.weight >= weight; });
|
||
for (; it != matching_family_fonts.end() && it->key.weight <= 500; ++it) {
|
||
if (auto found_font = it->font_with_point_size(font_size_in_pt, variations))
|
||
return found_font;
|
||
}
|
||
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, variations, false))
|
||
return found_font;
|
||
for (; it != matching_family_fonts.end(); ++it) {
|
||
if (auto found_font = it->font_with_point_size(font_size_in_pt, variations))
|
||
return found_font;
|
||
}
|
||
}
|
||
// If the desired weight is less than 400, weights less than or equal to the desired weight are checked in descending order
|
||
// followed by weights above the desired weight in ascending order until a match is found.
|
||
if (weight < 400) {
|
||
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, variations, true))
|
||
return found_font;
|
||
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, weight, font_size_in_pt, variations, false))
|
||
return found_font;
|
||
}
|
||
// If the desired weight is greater than 500, weights greater than or equal to the desired weight are checked in ascending order
|
||
// followed by weights below the desired weight in descending order until a match is found.
|
||
if (weight > 500) {
|
||
if (auto found_font = find_matching_font_weight_ascending(matching_family_fonts, weight, font_size_in_pt, variations, true))
|
||
return found_font;
|
||
if (auto found_font = find_matching_font_weight_descending(matching_family_fonts, weight, font_size_in_pt, variations, false))
|
||
return found_font;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
RefPtr<Gfx::FontCascadeList const> FontComputer::compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int slope, double font_weight, Percentage const& font_width, HashMap<FlyString, double> const& font_variation_settings) const
|
||
{
|
||
// FIXME: We round to int here as that is what is expected by our font infrastructure below
|
||
auto width = round_to<int>(font_width.value());
|
||
|
||
// FIXME: We round to int here as that is what is expected by our font infrastructure below
|
||
auto weight = round_to<int>(font_weight);
|
||
|
||
// FIXME: Implement the full font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
|
||
|
||
float const font_size_in_pt = font_size * 0.75f;
|
||
|
||
auto find_font = [&](FlyString const& family) -> RefPtr<Gfx::FontCascadeList const> {
|
||
FontFaceKey key {
|
||
.family_name = family,
|
||
.weight = weight,
|
||
.slope = slope,
|
||
};
|
||
auto result = Gfx::FontCascadeList::create();
|
||
if (auto it = m_loaded_fonts.find(key); it != m_loaded_fonts.end()) {
|
||
auto const& loaders = it->value;
|
||
|
||
Gfx::FontVariationSettings variation;
|
||
variation.set_weight(font_weight);
|
||
|
||
for (auto const& [tag_string, value] : font_variation_settings) {
|
||
auto string_view = tag_string.bytes_as_string_view();
|
||
if (string_view.length() != 4)
|
||
continue;
|
||
|
||
auto tag = Gfx::FourCC(string_view.characters_without_null_termination());
|
||
|
||
variation.axes.set(tag, value);
|
||
}
|
||
|
||
for (auto const& loader : loaders) {
|
||
if (auto found_font = loader->font_with_point_size(font_size_in_pt, variation))
|
||
result->add(*found_font, loader->unicode_ranges());
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
if (auto found_font = font_matching_algorithm(family, weight, slope, font_size_in_pt); found_font && !found_font->is_empty()) {
|
||
return found_font;
|
||
}
|
||
|
||
if (auto found_font = Gfx::FontDatabase::the().get(family, font_size_in_pt, weight, width, slope)) {
|
||
result->add(*found_font);
|
||
return result;
|
||
}
|
||
|
||
return {};
|
||
};
|
||
|
||
auto find_generic_font = [&](Keyword font_id) -> RefPtr<Gfx::FontCascadeList const> {
|
||
Platform::GenericFont generic_font {};
|
||
switch (font_id) {
|
||
case Keyword::Monospace:
|
||
case Keyword::UiMonospace:
|
||
generic_font = Platform::GenericFont::Monospace;
|
||
break;
|
||
case Keyword::Serif:
|
||
generic_font = Platform::GenericFont::Serif;
|
||
break;
|
||
case Keyword::Fantasy:
|
||
generic_font = Platform::GenericFont::Fantasy;
|
||
break;
|
||
case Keyword::SansSerif:
|
||
generic_font = Platform::GenericFont::SansSerif;
|
||
break;
|
||
case Keyword::Cursive:
|
||
generic_font = Platform::GenericFont::Cursive;
|
||
break;
|
||
case Keyword::UiSerif:
|
||
generic_font = Platform::GenericFont::UiSerif;
|
||
break;
|
||
case Keyword::UiSansSerif:
|
||
generic_font = Platform::GenericFont::UiSansSerif;
|
||
break;
|
||
case Keyword::UiRounded:
|
||
generic_font = Platform::GenericFont::UiRounded;
|
||
break;
|
||
default:
|
||
return {};
|
||
}
|
||
return find_font(Platform::FontPlugin::the().generic_font_name(generic_font));
|
||
};
|
||
|
||
auto font_list = Gfx::FontCascadeList::create();
|
||
|
||
for (auto const& family : font_family.as_value_list().values()) {
|
||
RefPtr<Gfx::FontCascadeList const> other_font_list;
|
||
if (family->is_keyword()) {
|
||
other_font_list = find_generic_font(family->to_keyword());
|
||
} else if (family->is_string()) {
|
||
other_font_list = find_font(family->as_string().string_value());
|
||
} else if (family->is_custom_ident()) {
|
||
other_font_list = find_font(family->as_custom_ident().custom_ident());
|
||
}
|
||
|
||
if (other_font_list)
|
||
font_list->extend(*other_font_list);
|
||
}
|
||
|
||
auto default_font = Platform::FontPlugin::the().default_font(font_size_in_pt);
|
||
if (font_list->is_empty()) {
|
||
// This is needed to make sure we check default font before reaching to emojis.
|
||
font_list->add(*default_font);
|
||
}
|
||
|
||
// Add emoji and symbol fonts
|
||
for (auto font_name : Platform::FontPlugin::the().symbol_font_names()) {
|
||
if (auto other_font_list = find_font(font_name)) {
|
||
font_list->extend(*other_font_list);
|
||
}
|
||
}
|
||
|
||
// The default font is already included in the font list, but we explicitly set it
|
||
// as the last-resort font. This ensures that if none of the specified fonts contain
|
||
// the requested code point, there is still a font available to provide a fallback glyph.
|
||
font_list->set_last_resort_font(*default_font);
|
||
|
||
return font_list;
|
||
}
|
||
|
||
Gfx::Font const& FontComputer::initial_font() const
|
||
{
|
||
// FIXME: This is not correct.
|
||
static auto font = ComputedProperties::font_fallback(false, false, 12);
|
||
return font;
|
||
}
|
||
|
||
void FontComputer::did_load_font(FlyString const&)
|
||
{
|
||
m_font_matching_algorithm_cache = {};
|
||
document().invalidate_style(DOM::StyleInvalidationReason::CSSFontLoaded);
|
||
}
|
||
|
||
GC::Ptr<FontLoader> FontComputer::load_font_face(ParsedFontFace const& font_face, Function<void(RefPtr<Gfx::Typeface const>)> on_load)
|
||
{
|
||
if (font_face.sources().is_empty()) {
|
||
if (on_load)
|
||
on_load({});
|
||
return {};
|
||
}
|
||
|
||
FontFaceKey key {
|
||
.family_name = font_face.font_family(),
|
||
.weight = font_face.weight().value_or(0),
|
||
.slope = font_face.slope().value_or(0),
|
||
};
|
||
|
||
// FIXME: Pass the sources directly, so the font loader can make use of the format information, or load local fonts.
|
||
Vector<URL> urls;
|
||
for (auto const& source : font_face.sources()) {
|
||
if (source.local_or_url.has<URL>())
|
||
urls.append(source.local_or_url.get<URL>());
|
||
// FIXME: Handle local()
|
||
}
|
||
|
||
if (urls.is_empty()) {
|
||
if (on_load)
|
||
on_load({});
|
||
return {};
|
||
}
|
||
|
||
auto loader = heap().allocate<FontLoader>(*this, font_face.parent_style_sheet(), font_face.font_family(), font_face.unicode_ranges(), move(urls), move(on_load));
|
||
auto& loader_ref = *loader;
|
||
auto maybe_font_loaders_list = m_loaded_fonts.get(key);
|
||
if (maybe_font_loaders_list.has_value()) {
|
||
maybe_font_loaders_list->append(move(loader));
|
||
} else {
|
||
FontLoaderList loaders;
|
||
loaders.append(loader);
|
||
m_loaded_fonts.set(OwnFontFaceKey(key), move(loaders));
|
||
}
|
||
// Actual object owned by font loader list inside m_loaded_fonts, this isn't use-after-move/free
|
||
return loader_ref;
|
||
}
|
||
|
||
void FontComputer::load_fonts_from_sheet(CSSStyleSheet& sheet)
|
||
{
|
||
for (auto const& rule : sheet.rules()) {
|
||
if (!is<CSSFontFaceRule>(*rule))
|
||
continue;
|
||
auto const& font_face_rule = static_cast<CSSFontFaceRule const&>(*rule);
|
||
if (!font_face_rule.is_valid())
|
||
continue;
|
||
if (auto font_loader = load_font_face(font_face_rule.font_face())) {
|
||
sheet.add_associated_font_loader(*font_loader);
|
||
}
|
||
}
|
||
}
|
||
|
||
void FontComputer::unload_fonts_from_sheet(CSSStyleSheet& sheet)
|
||
{
|
||
for (auto& [_, font_loader_list] : m_loaded_fonts) {
|
||
font_loader_list.remove_all_matching([&](auto& font_loader) {
|
||
return sheet.has_associated_font_loader(*font_loader);
|
||
});
|
||
}
|
||
}
|
||
|
||
size_t FontComputer::number_of_css_font_faces_with_loading_in_progress() const
|
||
{
|
||
size_t count = 0;
|
||
for (auto const& [_, loaders] : m_loaded_fonts) {
|
||
for (auto const& loader : loaders) {
|
||
if (loader->is_loading())
|
||
++count;
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
}
|