diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 5f12f001c46b..ed4cf0c6356a 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 897727115a70..8586a6a9d7e4 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/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index ed22ac6fff80..af827522cfdd 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -142,11 +143,19 @@ void ComputedProperties::set_property(PropertyID id, NonnullRefPtr value) { VERIFY(id >= first_longhand_property_id && id <= last_longhand_property_id); m_property_values[to_underlying(id) - to_underlying(first_longhand_property_id)] = move(value); + + if (property_affects_computed_font_list(id)) + clear_computed_font_list_cache(); } void ComputedProperties::revert_property(PropertyID id, ComputedProperties const& style_for_revert) @@ -173,6 +182,9 @@ void ComputedProperties::set_animated_property(PropertyID id, NonnullRefPtr ComputedProperties::font_feature_settings() const return {}; } -Optional> ComputedProperties::font_variation_settings() const +HashMap ComputedProperties::font_variation_settings() const { auto const& value = property(PropertyID::FontVariationSettings); @@ -1968,7 +1980,7 @@ Optional> ComputedProperties::font_variat if (value.is_value_list()) { auto const& axis_tags = value.as_value_list().values(); - HashMap result; + HashMap result; result.ensure_capacity(axis_tags.size()); for (auto const& tag_value : axis_tags) { auto const& axis_tag = tag_value->as_open_type_tagged(); @@ -1977,7 +1989,7 @@ Optional> ComputedProperties::font_variat result.set(axis_tag.tag(), axis_tag.value()->as_number().number()); } else { VERIFY(axis_tag.value()->is_calculated()); - result.set(axis_tag.tag(), NumberOrCalculated { axis_tag.value()->as_calculated() }); + result.set(axis_tag.tag(), axis_tag.value()->as_calculated().resolve_number({}).value()); } } return result; @@ -2563,6 +2575,27 @@ WillChange ComputedProperties::will_change() const return WillChange::make_auto(); } +ValueComparingNonnullRefPtr ComputedProperties::computed_font_list(FontComputer const& font_computer) const +{ + if (!m_cached_computed_font_list) { + const_cast(this)->m_cached_computed_font_list = font_computer.compute_font_for_style_values(property(PropertyID::FontFamily), font_size(), font_slope(), font_weight(), font_width(), font_variation_settings()); + VERIFY(!m_cached_computed_font_list->is_empty()); + } + + return *m_cached_computed_font_list; +} + +ValueComparingNonnullRefPtr ComputedProperties::first_available_computed_font(FontComputer const& font_computer) const +{ + if (!m_cached_first_available_computed_font) { + // https://drafts.csswg.org/css-fonts/#first-available-font + // First font for which the character U+0020 (space) is not excluded by a unicode-range + const_cast(this)->m_cached_first_available_computed_font = computed_font_list(font_computer)->font_for_code_point(' '); + } + + return *m_cached_first_available_computed_font; +} + CSSPixels ComputedProperties::font_size() const { return property(PropertyID::FontSize).as_length().length().absolute_length_to_px(); diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index faafa7d97e5f..2d1bb266ce98 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -166,7 +166,7 @@ class WEB_API ComputedProperties final : public JS::Cell { FontKerning font_kerning() const; Optional font_language_override() const; HashMap font_feature_settings() const; - Optional> font_variation_settings() const; + HashMap font_variation_settings() const; GridTrackSizeList grid_auto_columns() const; GridTrackSizeList grid_auto_rows() const; GridTrackSizeList grid_template_columns() const; @@ -234,25 +234,9 @@ class WEB_API ComputedProperties final : public JS::Cell { WillChange will_change() const; - Gfx::FontCascadeList const& computed_font_list() const - { - VERIFY(m_font_list); - return *m_font_list; - } - - Gfx::Font const& first_available_computed_font() const - { - VERIFY(m_first_available_computed_font); - return *m_first_available_computed_font; - } - - void set_computed_font_list(NonnullRefPtr font_list) - { - m_font_list = move(font_list); - // https://drafts.csswg.org/css-fonts/#first-available-font - // First font for which the character U+0020 (space) is not excluded by a unicode-range - m_first_available_computed_font = m_font_list->font_for_code_point(' '); - } + ValueComparingRefPtr cached_computed_font_list() const { return m_cached_computed_font_list; } + ValueComparingNonnullRefPtr computed_font_list(FontComputer const&) const; + ValueComparingNonnullRefPtr first_available_computed_font(FontComputer const&) const; [[nodiscard]] CSSPixels line_height() const; [[nodiscard]] CSSPixels font_size() const; @@ -306,8 +290,14 @@ class WEB_API ComputedProperties final : public JS::Cell { Display m_display_before_box_type_transformation { InitialValues::display() }; int m_math_depth { InitialValues::math_depth() }; - RefPtr m_font_list; - RefPtr m_first_available_computed_font; + + RefPtr m_cached_computed_font_list; + RefPtr m_cached_first_available_computed_font; + void clear_computed_font_list_cache() + { + m_cached_computed_font_list = nullptr; + m_cached_first_available_computed_font = nullptr; + } Optional m_line_height; diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index 2b38b5c71b29..87f623a291d0 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -663,7 +663,7 @@ class ComputedValues { double font_weight() const { return m_inherited.font_weight; } Gfx::ShapeFeatures font_features() const { return m_inherited.font_features; } Optional font_language_override() const { return m_inherited.font_language_override; } - Optional> font_variation_settings() const { return m_inherited.font_variation_settings; } + HashMap font_variation_settings() const { return m_inherited.font_variation_settings; } CSSPixels line_height() const { return m_inherited.line_height; } Time transition_delay() const { return m_noninherited.transition_delay; } @@ -700,7 +700,7 @@ class ComputedValues { double font_weight { InitialValues::font_weight() }; Gfx::ShapeFeatures font_features { InitialValues::font_features() }; Optional font_language_override; - Optional> font_variation_settings; + HashMap font_variation_settings; CSSPixels line_height { InitialValues::line_height() }; BorderCollapse border_collapse { InitialValues::border_collapse() }; EmptyCells empty_cells { InitialValues::empty_cells() }; @@ -906,7 +906,7 @@ class MutableComputedValues final : public ComputedValues { void set_font_weight(double font_weight) { m_inherited.font_weight = font_weight; } void set_font_features(Gfx::ShapeFeatures font_features) { m_inherited.font_features = move(font_features); } void set_font_language_override(Optional font_language_override) { m_inherited.font_language_override = move(font_language_override); } - void set_font_variation_settings(Optional> value) { m_inherited.font_variation_settings = move(value); } + void set_font_variation_settings(HashMap value) { m_inherited.font_variation_settings = move(value); } void set_line_height(CSSPixels line_height) { m_inherited.line_height = line_height; } void set_border_spacing_horizontal(Length border_spacing_horizontal) { m_inherited.border_spacing_horizontal = move(border_spacing_horizontal); } void set_border_spacing_vertical(Length border_spacing_vertical) { m_inherited.border_spacing_vertical = move(border_spacing_vertical); } diff --git a/Libraries/LibWeb/CSS/FontComputer.cpp b/Libraries/LibWeb/CSS/FontComputer.cpp new file mode 100644 index 000000000000..9c58ab172c6c --- /dev/null +++ b/Libraries/LibWeb/CSS/FontComputer.cpp @@ -0,0 +1,583 @@ +/* + * 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 {}; +} + +NonnullRefPtr 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(); + if (font_family.is_value_list()) { + auto const& family_list = static_cast(font_family).values(); + for (auto const& family : family_list) { + 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); + } + } else if (font_family.is_keyword()) { + if (auto other_font_list = find_generic_font(font_family.to_keyword())) + font_list->extend(*other_font_list); + } else if (font_family.is_string()) { + if (auto other_font_list = find_font(font_family.as_string().string_value())) + font_list->extend(*other_font_list); + } else if (font_family.is_custom_ident()) { + if (auto other_font_list = find_font(font_family.as_custom_ident().custom_ident())) + 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 000000000000..3b32e483f6b2 --- /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&); + + NonnullRefPtr 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 97f521abd9d5..eaddacf33ba1 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 5f11b3306bf7..82feee9dbc7b 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 @@ -132,14 +132,14 @@ Length::ResolutionContext Length::ResolutionContext::for_element(DOM::AbstractEl return Length::ResolutionContext { .viewport_rect = element.element().navigable()->viewport_rect(), - .font_metrics = { element.computed_properties()->font_size(), element.computed_properties()->first_available_computed_font().pixel_metrics(), element.computed_properties()->line_height() }, - .root_font_metrics = { root_element->computed_properties()->font_size(), root_element->computed_properties()->first_available_computed_font().pixel_metrics(), element.computed_properties()->line_height() } + .font_metrics = { element.computed_properties()->font_size(), element.computed_properties()->first_available_computed_font(element.document().font_computer())->pixel_metrics(), element.computed_properties()->line_height() }, + .root_font_metrics = { root_element->computed_properties()->font_size(), root_element->computed_properties()->first_available_computed_font(element.document().font_computer())->pixel_metrics(), element.computed_properties()->line_height() } }; } 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 69945eeb8063..311288c20a88 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; @@ -904,7 +683,7 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element Length::FontMetrics font_metrics { computed_properties.font_size(), - computed_properties.first_available_computed_font().pixel_metrics(), + computed_properties.first_available_computed_font(document().font_computer())->pixel_metrics(), computed_properties.line_height() }; @@ -1018,7 +797,7 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element .viewport_rect = viewport_rect(), .font_metrics = { computed_properties.font_size(), - computed_properties.first_available_computed_font().pixel_metrics(), + computed_properties.first_available_computed_font(document().font_computer())->pixel_metrics(), inheritance_parent_has_computed_properties ? inheritance_parent->computed_properties()->line_height() : InitialValues::line_height() }, .root_font_metrics = m_root_element_font_metrics }, .abstract_element = abstract_element @@ -1634,7 +1413,7 @@ Length::FontMetrics StyleComputer::calculate_root_element_font_metrics(ComputedP { auto const& root_value = style.property(CSS::PropertyID::FontSize); - auto font_pixel_metrics = style.first_available_computed_font().pixel_metrics(); + auto font_pixel_metrics = style.first_available_computed_font(document().font_computer())->pixel_metrics(); Length::FontMetrics font_metrics { m_default_font_metrics.font_size, font_pixel_metrics, InitialValues::line_height() }; font_metrics.font_size = root_value.as_length().length().to_px(viewport_rect(), font_metrics, font_metrics); font_metrics.line_height = style.line_height(); @@ -1642,115 +1421,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. @@ -1807,149 +1477,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, Length::ResolutionContext const& length_resolution_context) 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); - - CalculationResolutionContext context { - .length_resolution_context = length_resolution_context, - }; - - 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()); - - auto resolved_value = value.resolved(context); - if (!resolved_value.has_value()) - continue; - - variation.axes.set(tag, resolved_value.release_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(); - if (font_family.is_value_list()) { - auto const& family_list = static_cast(font_family).values(); - for (auto const& family : family_list) { - 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); - } - } else if (font_family.is_keyword()) { - if (auto other_font_list = find_generic_font(font_family.to_keyword())) - font_list->extend(*other_font_list); - } else if (font_family.is_string()) { - if (auto other_font_list = find_font(font_family.as_string().string_value())) - font_list->extend(*other_font_list); - } else if (font_family.is_custom_ident()) { - if (auto other_font_list = find_font(font_family.as_custom_ident().custom_ident())) - 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 {}; @@ -1996,15 +1523,7 @@ void StyleComputer::compute_font(ComputedProperties& style, Optionalis_empty()); - - RefPtr const found_font = font_list->first(); - - style.set_computed_font_list(*font_list); + RefPtr const found_font = style.first_available_computed_font(m_document->font_computer()); Length::FontMetrics line_height_font_metrics { style.font_size(), @@ -2071,18 +1590,11 @@ 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 { style.font_size(), - style.first_available_computed_font().pixel_metrics(), + style.first_available_computed_font(document().font_computer())->pixel_metrics(), style.line_height() }; @@ -2655,77 +2167,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- @@ -3451,18 +2892,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 9eb11b5b286a..be967593fbbf 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 @@ class CountingBloomFilter { 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 @@ class WEB_API StyleComputer final : public GC::Cell { 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, Length::ResolutionContext const& length_resolution_context) 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 @@ class WEB_API StyleComputer final : public GC::Cell { CreatePseudoElementStyleIfNeeded, }; - struct MatchingFontCandidate; - struct LayerMatchingRules { FlyString qualified_layer_name; Vector rules; @@ -208,10 +164,6 @@ class WEB_API StyleComputer final : public GC::Cell { 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 @@ class WEB_API StyleComputer final : public GC::Cell { [[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 e5fa688704e3..cf58f8fc425d 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -465,6 +466,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)) @@ -567,6 +569,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 7900bc18a868..939fae74eb8d 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -260,6 +260,9 @@ class WEB_API Document 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; @@ -1008,6 +1011,7 @@ class WEB_API Document 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/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 959ab46b4362..9d01aa6cf6a7 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -699,7 +699,7 @@ static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(C { CSS::RequiredInvalidationAfterStyleChange invalidation; - if (!old_style.computed_font_list().equals(new_style.computed_font_list())) + if (old_style.cached_computed_font_list() != new_style.cached_computed_font_list()) invalidation.relayout = true; for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) { diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index c7929c5b2ce2..5b7cd8c8d729 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 0f0920062eb1..6d18001c946b 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 064d45e62a27..a0f319de7bf0 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,14 +143,13 @@ 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(), computed_font_weight->as_number().number(), computed_font_width->as_percentage().percentage(), - {}, - length_resolution_context); + {}); }, [](HTML::WorkerGlobalScope*) -> RefPtr { // FIXME: implement computing the font for HTML::WorkerGlobalScope diff --git a/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 73d00503ae33..a37616c548ad 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(); } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index ad459796c111..dd81b1e49d3c 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -150,7 +150,7 @@ void HTMLInputElement::adjust_computed_style(CSS::ComputedProperties& style) // NOTE: Other browsers apply a minimum height of a single line's line-height to single-line input elements. if (is_single_line() && style.property(CSS::PropertyID::Height).has_auto()) { auto current_line_height = style.line_height().to_double(); - auto minimum_line_height = style.first_available_computed_font().pixel_size() * CSS::ComputedProperties::normal_line_height_scale; + auto minimum_line_height = style.first_available_computed_font(document().font_computer())->pixel_size() * CSS::ComputedProperties::normal_line_height_scale; // FIXME: Instead of overriding line-height, we should set height here instead. if (current_line_height < minimum_line_height) diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index 89c87d41f5b4..401231c25098 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -397,7 +397,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) // NOTE: We have to be careful that font-related properties get set in the right order. // m_font is used by Length::to_px() when resolving sizes against this layout node. // That's why it has to be set before everything else. - computed_values.set_font_list(computed_style.computed_font_list()); + computed_values.set_font_list(computed_style.computed_font_list(document().font_computer())); computed_values.set_font_size(computed_style.font_size()); computed_values.set_font_weight(computed_style.font_weight()); computed_values.set_line_height(computed_style.line_height()); @@ -550,8 +550,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) if (auto maybe_font_language_override = computed_style.font_language_override(); maybe_font_language_override.has_value()) computed_values.set_font_language_override(maybe_font_language_override.release_value()); computed_values.set_font_features(computed_style.font_features()); - if (auto maybe_font_variation_settings = computed_style.font_variation_settings(); maybe_font_variation_settings.has_value()) - computed_values.set_font_variation_settings(maybe_font_variation_settings.release_value()); + computed_values.set_font_variation_settings(computed_style.font_variation_settings()); auto const& border_bottom_left_radius = computed_style.property(CSS::PropertyID::BorderBottomLeftRadius); if (border_bottom_left_radius.is_border_radius()) { diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 256ee03df797..ce60265a4dab 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -540,7 +540,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodePropert auto serialize_used_fonts = [&]() { JsonArray serialized; - properties->computed_font_list().for_each_font_entry([&](Gfx::FontCascadeList::Entry const& entry) { + properties->computed_font_list(node->document().font_computer())->for_each_font_entry([&](Gfx::FontCascadeList::Entry const& entry) { auto const& font = *entry.font; JsonObject font_object; diff --git a/Tests/LibWeb/Ref/expected/css/animated-font-size-ref.html b/Tests/LibWeb/Ref/expected/css/animated-font-size-ref.html new file mode 100644 index 000000000000..87d37689985b --- /dev/null +++ b/Tests/LibWeb/Ref/expected/css/animated-font-size-ref.html @@ -0,0 +1,2 @@ + +
TEST
diff --git a/Tests/LibWeb/Ref/input/css/animated-font-size.html b/Tests/LibWeb/Ref/input/css/animated-font-size.html new file mode 100644 index 000000000000..36c6ab6c0da8 --- /dev/null +++ b/Tests/LibWeb/Ref/input/css/animated-font-size.html @@ -0,0 +1,9 @@ + + +
TEST
+