From 6c236d04d8bdf9367cc696a659d8bddc71bee223 Mon Sep 17 00:00:00 2001 From: Callum Law Date: Thu, 6 Nov 2025 18:27:22 +1300 Subject: [PATCH] LibWeb: Separate font computation logic from StyleComputer 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. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/CSS/CSSStyleSheet.cpp | 5 +- Libraries/LibWeb/CSS/FontComputer.cpp | 573 ++++++++++++++++++ Libraries/LibWeb/CSS/FontComputer.h | 124 ++++ Libraries/LibWeb/CSS/FontFace.cpp | 6 +- Libraries/LibWeb/CSS/Length.cpp | 6 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 550 +---------------- Libraries/LibWeb/CSS/StyleComputer.h | 87 --- Libraries/LibWeb/DOM/Document.cpp | 3 + Libraries/LibWeb/DOM/Document.h | 4 + Libraries/LibWeb/Forward.h | 2 + .../HTML/Canvas/CanvasTextDrawingStyles.cpp | 3 +- Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp | 4 +- 13 files changed, 722 insertions(+), 646 deletions(-) create mode 100644 Libraries/LibWeb/CSS/FontComputer.cpp create mode 100644 Libraries/LibWeb/CSS/FontComputer.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index b635a4b1429..0426e66f313 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -162,6 +162,7 @@ set(SOURCES CSS/EdgeRect.cpp CSS/Fetch.cpp CSS/Flex.cpp + CSS/FontComputer.cpp CSS/FontFace.cpp CSS/FontFaceSet.cpp CSS/Frequency.cpp diff --git a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp index 897727115a7..8586a6a9d7e 100644 --- a/Libraries/LibWeb/CSS/CSSStyleSheet.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleSheet.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -325,7 +326,7 @@ void CSSStyleSheet::add_owning_document_or_shadow_root(DOM::Node& document_or_sh // All owning documents or shadow roots must be part of the same document so we only need to load this style // sheet's fonts against the document of the first if (this->owning_documents_or_shadow_roots().size() == 1) - document_or_shadow_root.document().style_computer().load_fonts_from_sheet(*this); + document_or_shadow_root.document().font_computer().load_fonts_from_sheet(*this); for (auto const& import_rule : m_import_rules) { if (import_rule->loaded_style_sheet()) @@ -340,7 +341,7 @@ void CSSStyleSheet::remove_owning_document_or_shadow_root(DOM::Node& document_or // All owning documents or shadow roots must be part of the same document so we only need to unload this style // sheet's fonts once we have none remaining. if (this->owning_documents_or_shadow_roots().size() == 0) - document_or_shadow_root.document().style_computer().unload_fonts_from_sheet(*this); + document_or_shadow_root.document().font_computer().unload_fonts_from_sheet(*this); for (auto const& import_rule : m_import_rules) { if (import_rule->loaded_style_sheet()) diff --git a/Libraries/LibWeb/CSS/FontComputer.cpp b/Libraries/LibWeb/CSS/FontComputer.cpp new file mode 100644 index 00000000000..689f1a849ad --- /dev/null +++ b/Libraries/LibWeb/CSS/FontComputer.cpp @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2018-2025, Andreas Kling + * Copyright (c) 2021, the SerenityOS developers. + * Copyright (c) 2021-2025, Sam Atkins + * Copyright (c) 2024, Matthew Olsson + * Copyright (c) 2025, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "FontComputer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::CSS { + +GC_DEFINE_ALLOCATOR(FontComputer); +GC_DEFINE_ALLOCATOR(FontLoader); + +struct FontFaceKey { + NonnullRawPtr family_name; + int weight { 0 }; + int slope { 0 }; +}; + +} + +namespace AK { + +namespace Detail { + +template<> +inline constexpr bool IsHashCompatible = true; +template<> +inline constexpr bool IsHashCompatible = true; + +} + +template<> +struct Traits : public DefaultTraits { + 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 : public DefaultTraits { + 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 : public DefaultTraits { + 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::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 parent_style_sheet, FlyString family_name, Vector unicode_ranges, Vector urls, Function)> 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 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 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 typeface; + if (auto* bytes = stream.template get_pointer()) { + 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 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> 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 loader_or_typeface; + + [[nodiscard]] RefPtr 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(); 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()->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 FontComputer::find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive) +{ + using Fn = AK::Function; + 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 FontComputer::find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive) +{ + using Fn = AK::Function; + 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 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 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 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(&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(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 FontComputer::compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int slope, double font_weight, Percentage const& font_width, HashMap 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(font_width.value()); + + // FIXME: We round to int here as that is what is expected by our font infrastructure below + auto weight = round_to(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 { + 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 { + 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 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 FontComputer::load_font_face(ParsedFontFace const& font_face, Function)> 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 urls; + for (auto const& source : font_face.sources()) { + if (source.local_or_url.has()) + urls.append(source.local_or_url.get()); + // FIXME: Handle local() + } + + if (urls.is_empty()) { + if (on_load) + on_load({}); + return {}; + } + + auto loader = heap().allocate(*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(*rule)) + continue; + auto const& font_face_rule = static_cast(*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; +} + +} diff --git a/Libraries/LibWeb/CSS/FontComputer.h b/Libraries/LibWeb/CSS/FontComputer.h new file mode 100644 index 00000000000..bc4534d3420 --- /dev/null +++ b/Libraries/LibWeb/CSS/FontComputer.h @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018-2025, Andreas Kling + * Copyright (c) 2021, the SerenityOS developers. + * Copyright (c) 2021-2025, Sam Atkins + * Copyright (c) 2024, Matthew Olsson + * Copyright (c) 2025, Callum Law + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +#pragma once + +namespace Web::CSS { + +struct FontFaceKey; + +struct OwnFontFaceKey { + explicit OwnFontFaceKey(FontFaceKey const& other); + + operator FontFaceKey() const; + + [[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); } + [[nodiscard]] bool operator==(OwnFontFaceKey const& other) const = default; + [[nodiscard]] bool operator==(FontFaceKey const& other) const; + + FlyString family_name; + int weight { 0 }; + int slope { 0 }; +}; + +struct FontMatchingAlgorithmCacheKey { + FlyString family_name; + int weight; + int slope; + float font_size_in_pt; + + [[nodiscard]] bool operator==(FontMatchingAlgorithmCacheKey const& other) const = default; +}; + +class FontLoader final : public GC::Cell { + GC_CELL(FontLoader, GC::Cell); + GC_DECLARE_ALLOCATOR(FontLoader); + +public: + FontLoader(FontComputer& font_computer, GC::Ptr parent_style_sheet, FlyString family_name, Vector unicode_ranges, Vector urls, ESCAPING Function)> on_load = {}); + + virtual ~FontLoader(); + + Vector const& unicode_ranges() const { return m_unicode_ranges; } + RefPtr vector_font() const { return m_vector_font; } + + RefPtr font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations = {}); + void start_loading_next_url(); + + bool is_loading() const; + +private: + virtual void visit_edges(Visitor&) override; + + ErrorOr> try_load_font(Fetch::Infrastructure::Response const&, ByteBuffer const&); + + void font_did_load_or_fail(RefPtr); + + GC::Ref m_font_computer; + GC::Ptr m_parent_style_sheet; + FlyString m_family_name; + Vector m_unicode_ranges; + RefPtr m_vector_font; + Vector m_urls; + GC::Ptr m_fetch_controller; + Function)> m_on_load; +}; + +class WEB_API FontComputer final : public GC::Cell { + GC_CELL(FontComputer, GC::Cell); + GC_DECLARE_ALLOCATOR(FontComputer); + +public: + explicit FontComputer(DOM::Document& document) + : m_document(document) + { + } + + ~FontComputer() = default; + + DOM::Document& document() { return m_document; } + DOM::Document const& document() const { return m_document; } + + Gfx::Font const& initial_font() const; + + void did_load_font(FlyString const& family_name); + + GC::Ptr load_font_face(ParsedFontFace const&, ESCAPING Function)> on_load = {}); + + void load_fonts_from_sheet(CSSStyleSheet&); + void unload_fonts_from_sheet(CSSStyleSheet&); + + RefPtr compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int font_slope, double font_weight, Percentage const& font_width, HashMap const& font_variation_settings) const; + + size_t number_of_css_font_faces_with_loading_in_progress() const; + +private: + virtual void visit_edges(Visitor&) override; + + struct MatchingFontCandidate; + static RefPtr find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); + static RefPtr find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); + RefPtr font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; + RefPtr font_matching_algorithm_impl(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; + + GC::Ref m_document; + + using FontLoaderList = Vector>; + HashMap m_loaded_fonts; + + mutable HashMap> m_font_matching_algorithm_cache; +}; + +} diff --git a/Libraries/LibWeb/CSS/FontFace.cpp b/Libraries/LibWeb/CSS/FontFace.cpp index 97f521abd9d..eaddacf33ba 100644 --- a/Libraries/LibWeb/CSS/FontFace.cpp +++ b/Libraries/LibWeb/CSS/FontFace.cpp @@ -16,9 +16,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -483,7 +483,7 @@ GC::Ref FontFace::load() // FIXME: We should probably put the 'font cache' on the WindowOrWorkerGlobalScope instead of tying it to the document's style computer auto& global = HTML::relevant_global_object(*font); if (auto* window = as_if(global)) { - auto& style_computer = const_cast(window->document()->style_computer()); + auto& font_computer = const_cast(window->document()->font_computer()); // FIXME: The ParsedFontFace is kind of expensive to create. We should be using a shared sub-object for the data ParsedFontFace parsed_font_face { @@ -503,7 +503,7 @@ GC::Ref FontFace::load() {}, // FIXME: feature_settings {}, // FIXME: variation_settings }; - if (auto loader = style_computer.load_font_face(parsed_font_face, move(on_load))) + if (auto loader = font_computer.load_font_face(parsed_font_face, move(on_load))) loader->start_loading_next_url(); } else { // FIXME: Don't know how to load fonts in workers! They don't have a StyleComputer diff --git a/Libraries/LibWeb/CSS/Length.cpp b/Libraries/LibWeb/CSS/Length.cpp index 5f11b3306bf..edf482fc55f 100644 --- a/Libraries/LibWeb/CSS/Length.cpp +++ b/Libraries/LibWeb/CSS/Length.cpp @@ -11,9 +11,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -139,7 +139,7 @@ Length::ResolutionContext Length::ResolutionContext::for_element(DOM::AbstractEl Length::ResolutionContext Length::ResolutionContext::for_window(HTML::Window const& window) { - auto const& initial_font = window.associated_document().style_computer().initial_font(); + auto const& initial_font = window.associated_document().font_computer().initial_font(); Gfx::FontPixelMetrics const& initial_font_metrics = initial_font.pixel_metrics(); Length::FontMetrics font_metrics { CSSPixels { initial_font.pixel_size() }, initial_font_metrics, InitialValues::line_height() }; return Length::ResolutionContext { @@ -151,7 +151,7 @@ Length::ResolutionContext Length::ResolutionContext::for_window(HTML::Window con Length::ResolutionContext Length::ResolutionContext::for_document(DOM::Document const& document) { - auto const& initial_font = document.style_computer().initial_font(); + auto const& initial_font = document.font_computer().initial_font(); Gfx::FontPixelMetrics const& initial_font_metrics = initial_font.pixel_metrics(); Length::FontMetrics font_metrics { CSSPixels { initial_font.pixel_size() }, initial_font_metrics, InitialValues::line_height() }; CSSPixelRect viewport_rect; diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 798f32b31e7..1fb958e4922 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -17,19 +17,12 @@ #include #include #include -#include #include -#include -#include -#include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -38,7 +31,7 @@ #include #include #include -#include +#include #include #include #include @@ -82,17 +75,12 @@ #include #include #include -#include -#include -#include #include #include #include #include #include #include -#include -#include #include #include #include @@ -103,52 +91,6 @@ namespace Web::CSS { GC_DEFINE_ALLOCATOR(StyleComputer); -GC_DEFINE_ALLOCATOR(FontLoader); - -struct FontFaceKey { - NonnullRawPtr family_name; - int weight { 0 }; - int slope { 0 }; -}; - -} - -namespace AK { - -namespace Detail { - -template<> -inline constexpr bool IsHashCompatible = true; -template<> -inline constexpr bool IsHashCompatible = true; - -} - -template<> -struct Traits : public DefaultTraits { - 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 : public DefaultTraits { - 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 : public DefaultTraits { - 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::hash(key.font_size_in_pt)); - return hash; - } -}; - -} - -namespace Web::CSS { CSSStyleProperties const& MatchingRule::declaration() const { @@ -177,29 +119,6 @@ FlyString const& MatchingRule::qualified_layer_name() const VERIFY_NOT_REACHED(); } -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; -} - StyleComputer::StyleComputer(DOM::Document& document) : m_document(document) , m_default_font_metrics(16, Platform::FontPlugin::the().default_font(16)->pixel_metrics(), InitialValues::line_height()) @@ -214,148 +133,8 @@ void StyleComputer::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_document); - visitor.visit(m_loaded_fonts); } -FontLoader::FontLoader(StyleComputer& style_computer, GC::Ptr parent_style_sheet, FlyString family_name, Vector unicode_ranges, Vector urls, Function)> on_load) - : m_style_computer(style_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_style_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 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 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_style_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 typeface; - if (auto* bytes = stream.template get_pointer()) { - 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 typeface) -{ - if (typeface) { - m_vector_font = typeface.release_nonnull(); - m_style_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> 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 StyleComputer::MatchingFontCandidate { - FontFaceKey key; - Variant loader_or_typeface; - - [[nodiscard]] RefPtr font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations) const - { - auto font_list = Gfx::FontCascadeList::create(); - if (auto* loader_list = loader_or_typeface.get_pointer(); 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()->font(point_size, variations)); - return font_list; - } -}; - Optional StyleComputer::user_agent_style_sheet_source(StringView name) { extern String default_stylesheet_source; @@ -1646,115 +1425,6 @@ Length::FontMetrics StyleComputer::calculate_root_element_font_metrics(ComputedP return font_metrics; } -RefPtr StyleComputer::find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive) -{ - using Fn = AK::Function; - 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 StyleComputer::find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive) -{ - using Fn = AK::Function; - 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 StyleComputer::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 StyleComputer::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 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(&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(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 {}; -} - CSSPixels StyleComputer::default_user_font_size() { // FIXME: This value should be configurable by the user. @@ -1811,132 +1481,6 @@ CSSPixels StyleComputer::relative_size_mapping(RelativeSize relative_size, CSSPi VERIFY_NOT_REACHED(); } -RefPtr StyleComputer::compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int slope, double font_weight, Percentage const& font_width, HashMap 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(font_width.value()); - - // FIXME: We round to int here as that is what is expected by our font infrastructure below - auto weight = round_to(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 { - 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 { - 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 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; -} - void StyleComputer::compute_font(ComputedProperties& style, Optional abstract_element) const { auto const& inheritance_parent = abstract_element.has_value() ? abstract_element->element_to_inherit_style_from() : OptionalNone {}; @@ -1985,7 +1529,7 @@ void StyleComputer::compute_font(ComputedProperties& style, Optionalis_empty()); @@ -2058,13 +1602,6 @@ LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context( }; } -Gfx::Font const& StyleComputer::initial_font() const -{ - // FIXME: This is not correct. - static auto font = ComputedProperties::font_fallback(false, false, 12); - return font; -} - void StyleComputer::compute_property_values(ComputedProperties& style, Optional abstract_element) const { Length::FontMetrics font_metrics { @@ -2640,77 +2177,6 @@ static Optional is_roundabout_selector_bucketabl return {}; } -void StyleComputer::did_load_font(FlyString const&) -{ - m_font_matching_algorithm_cache = {}; - document().invalidate_style(DOM::StyleInvalidationReason::CSSFontLoaded); -} - -GC::Ptr StyleComputer::load_font_face(ParsedFontFace const& font_face, Function)> 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 urls; - for (auto const& source : font_face.sources()) { - if (source.local_or_url.has()) - urls.append(source.local_or_url.get()); - // FIXME: Handle local() - } - - if (urls.is_empty()) { - if (on_load) - on_load({}); - return {}; - } - - auto loader = heap().allocate(*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 StyleComputer::load_fonts_from_sheet(CSSStyleSheet& sheet) -{ - for (auto const& rule : sheet.rules()) { - if (!is(*rule)) - continue; - auto const& font_face_rule = static_cast(*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 StyleComputer::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); - }); - } -} - NonnullRefPtr StyleComputer::compute_value_of_custom_property(DOM::AbstractElement abstract_element, FlyString const& name, Optional guarded_contexts) { // https://drafts.csswg.org/css-variables/#propdef- @@ -3432,18 +2898,6 @@ void StyleComputer::pop_ancestor(DOM::Element const& element) }); } -size_t StyleComputer::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; -} - void RuleCache::add_rule(MatchingRule const& matching_rule, Optional pseudo_element, bool contains_root_pseudo_class) { if (matching_rule.slotted) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index d17afb85847..be967593fbb 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -10,8 +10,6 @@ #include #include #include -#include -#include #include #include #include @@ -23,7 +21,6 @@ #include #include #include -#include namespace Web::CSS { @@ -76,33 +73,6 @@ private: CounterType m_buckets[bucket_count]; }; -struct FontFaceKey; - -struct OwnFontFaceKey { - explicit OwnFontFaceKey(FontFaceKey const& other); - - operator FontFaceKey() const; - - [[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); } - [[nodiscard]] bool operator==(OwnFontFaceKey const& other) const = default; - [[nodiscard]] bool operator==(FontFaceKey const& other) const; - - FlyString family_name; - int weight { 0 }; - int slope { 0 }; -}; - -struct FontMatchingAlgorithmCacheKey { - FlyString family_name; - int weight; - int slope; - float font_size_in_pt; - - [[nodiscard]] bool operator==(FontMatchingAlgorithmCacheKey const& other) const = default; -}; - -class FontLoader; - class WEB_API StyleComputer final : public GC::Cell { GC_CELL(StyleComputer, GC::Cell); GC_DECLARE_ALLOCATOR(StyleComputer); @@ -138,27 +108,15 @@ public: InvalidationSet invalidation_set_for_properties(Vector const&, StyleScope const&) const; bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&, StyleScope const&) const; - Gfx::Font const& initial_font() const; - - void did_load_font(FlyString const& family_name); - - GC::Ptr load_font_face(ParsedFontFace const&, ESCAPING Function)> on_load = {}); - - void load_fonts_from_sheet(CSSStyleSheet&); - void unload_fonts_from_sheet(CSSStyleSheet&); - static CSSPixels default_user_font_size(); static CSSPixels absolute_size_mapping(AbsoluteSize, CSSPixels default_font_size); static CSSPixels relative_size_mapping(RelativeSize, CSSPixels inherited_font_size); - RefPtr compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int font_slope, double font_weight, Percentage const& font_width, HashMap const& font_variation_settings) const; [[nodiscard]] RefPtr recascade_font_size_if_needed(DOM::AbstractElement, CascadedProperties&) const; void set_viewport_rect(Badge, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; } void collect_animation_into(DOM::AbstractElement, GC::Ref animation, ComputedProperties&) const; - size_t number_of_css_font_faces_with_loading_in_progress() const; - [[nodiscard]] GC::Ref compute_properties(DOM::AbstractElement, CascadedProperties&) const; void compute_property_values(ComputedProperties&, Optional) const; @@ -190,8 +148,6 @@ private: CreatePseudoElementStyleIfNeeded, }; - struct MatchingFontCandidate; - struct LayerMatchingRules { FlyString qualified_layer_name; Vector rules; @@ -208,10 +164,6 @@ private: LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::AbstractElement, ComputeStyleMode, MatchingRuleSet const&) const; [[nodiscard]] GC::Ptr compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional did_change_custom_properties, StyleScope const&) const; [[nodiscard]] GC::Ref compute_cascaded_values(DOM::AbstractElement, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional, ReadonlySpan properties_to_cascade) const; - static RefPtr find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); - static RefPtr find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive); - RefPtr font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; - RefPtr font_matching_algorithm_impl(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const; void compute_custom_properties(ComputedProperties&, DOM::AbstractElement) const; void compute_math_depth(ComputedProperties&, Optional) const; void start_needed_transitions(ComputedProperties const& old_style, ComputedProperties& new_style, DOM::AbstractElement) const; @@ -236,51 +188,12 @@ private: [[nodiscard]] RuleCache const* rule_cache_for_cascade_origin(CascadeOrigin, Optional qualified_layer_name, GC::Ptr) const; - using FontLoaderList = Vector>; - HashMap m_loaded_fonts; - Length::FontMetrics m_default_font_metrics; Length::FontMetrics m_root_element_font_metrics; CSSPixelRect m_viewport_rect; OwnPtr> m_ancestor_filter; - - mutable HashMap> m_font_matching_algorithm_cache; -}; - -class FontLoader final : public GC::Cell { - GC_CELL(FontLoader, GC::Cell); - GC_DECLARE_ALLOCATOR(FontLoader); - -public: - FontLoader(StyleComputer& style_computer, GC::Ptr parent_style_sheet, FlyString family_name, Vector unicode_ranges, Vector urls, ESCAPING Function)> on_load = {}); - - virtual ~FontLoader(); - - Vector const& unicode_ranges() const { return m_unicode_ranges; } - RefPtr vector_font() const { return m_vector_font; } - - RefPtr font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations = {}); - void start_loading_next_url(); - - bool is_loading() const; - -private: - virtual void visit_edges(Visitor&) override; - - ErrorOr> try_load_font(Fetch::Infrastructure::Response const&, ByteBuffer const&); - - void font_did_load_or_fail(RefPtr); - - GC::Ref m_style_computer; - GC::Ptr m_parent_style_sheet; - FlyString m_family_name; - Vector m_unicode_ranges; - RefPtr m_vector_font; - Vector m_urls; - GC::Ptr m_fetch_controller; - Function)> m_on_load; }; inline bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 880fc676d00..14e274c8136 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -467,6 +468,7 @@ Document::Document(JS::Realm& realm, URL::URL const& url, TemporaryDocumentForFr : ParentNode(realm, *this, NodeType::DOCUMENT_NODE) , m_page(Bindings::principal_host_defined_page(realm)) , m_style_computer(realm.heap().allocate(*this)) + , m_font_computer(realm.heap().allocate(*this)) , m_url(url) , m_temporary_document_for_fragment_parsing(temporary_document_for_fragment_parsing) , m_editing_host_manager(EditingHostManager::create(realm, *this)) @@ -569,6 +571,7 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(m_pending_parsing_blocking_script); visitor.visit(m_history); visitor.visit(m_style_computer); + visitor.visit(m_font_computer); visitor.visit(m_browsing_context); visitor.visit(m_applets); diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 450147ec391..a8389fbd4a8 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -260,6 +260,9 @@ public: CSS::StyleComputer& style_computer() { return *m_style_computer; } CSS::StyleComputer const& style_computer() const { return *m_style_computer; } + CSS::FontComputer& font_computer() { return *m_font_computer; } + CSS::FontComputer const& font_computer() const { return *m_font_computer; } + CSS::StyleSheetList& style_sheets(); CSS::StyleSheetList const& style_sheets() const; @@ -1010,6 +1013,7 @@ private: GC::Ref m_page; GC::Ptr m_style_computer; + GC::Ptr m_font_computer; GC::Ptr m_style_sheets; GC::Ptr m_active_favicon; GC::Ptr m_browsing_context; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 4ca49c59420..96f8756aab1 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -16,6 +16,7 @@ namespace Web { +class CSSPixels; class DisplayListRecordingContext; class DragAndDropEventHandler; class EventHandler; @@ -294,6 +295,7 @@ class FitContentStyleValue; class Flex; class FlexOrCalculated; class FlexStyleValue; +class FontComputer; class FontFace; class FontFaceSet; class FontSourceStyleValue; diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.cpp b/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.cpp index 05d31adba76..a0f319de7bf 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.cpp +++ b/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.cpp @@ -7,6 +7,7 @@ #include "CanvasTextDrawingStyles.h" #include +#include #include #include #include @@ -142,7 +143,7 @@ void CanvasTextDrawingStyles::set_font(StringView fo auto const& computed_font_width = CSS::StyleComputer::compute_font_width(font_width, computation_context); auto const& computed_font_style = CSS::StyleComputer::compute_font_style(font_style, computation_context); - return document->style_computer().compute_font_for_style_values( + return document->font_computer().compute_font_for_style_values( font_family, computed_font_size->as_length().length().absolute_length_to_px(), computed_font_style->as_font_style().to_font_slope(), diff --git a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 73d00503ae3..a37616c548a 100644 --- a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -508,7 +508,7 @@ void EventLoop::update_the_rendering() } for (auto& document : docs) { - if (document->readiness() == HTML::DocumentReadyState::Complete && document->style_computer().number_of_css_font_faces_with_loading_in_progress() == 0) { + if (document->readiness() == HTML::DocumentReadyState::Complete && document->font_computer().number_of_css_font_faces_with_loading_in_progress() == 0) { HTML::TemporaryExecutionContext context(document->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); document->fonts()->resolve_ready_promise(); }