mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
574 lines
23 KiB
C++
574 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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|