From b7526a39b07ac596263f6acd2a684f339bd7d847 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Wed, 15 May 2024 14:58:24 -0600 Subject: [PATCH] LibWeb: Expose StyleComputer's FontLoader class publicly We'll want to explicitly load fonts from FontFace and other Web APIs in the future. A future refactor should also move this completely away from StyleComputer and call it something like 'FontCache'. --- .../Libraries/LibWeb/CSS/StyleComputer.cpp | 246 +++++++++--------- Userland/Libraries/LibWeb/CSS/StyleComputer.h | 33 ++- 2 files changed, 159 insertions(+), 120 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index ddac7c0414a..9f00538ff4c 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -72,7 +72,6 @@ #include #include #include -#include #include #include #include @@ -103,103 +102,98 @@ StyleComputer::StyleComputer(DOM::Document& document) StyleComputer::~StyleComputer() = default; -class StyleComputer::FontLoader : public ResourceClient { -public: - explicit FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector urls) - : m_style_computer(style_computer) - , m_family_name(move(family_name)) - , m_unicode_ranges(move(unicode_ranges)) - , m_urls(move(urls)) - { +FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector urls, Function on_load, Function on_fail) + : m_style_computer(style_computer) + , m_family_name(move(family_name)) + , m_unicode_ranges(move(unicode_ranges)) + , m_urls(move(urls)) + , m_on_load(move(on_load)) + , m_on_fail(move(on_fail)) +{ +} + +FontLoader::~FontLoader() = default; + +void FontLoader::resource_did_load() +{ + auto result = try_load_font(); + if (result.is_error()) { + dbgln("Failed to parse font: {}", result.error()); + start_loading_next_url(); + return; } + m_vector_font = result.release_value(); + m_style_computer.did_load_font(m_family_name); + if (m_on_load) + m_on_load(*this); +} - virtual ~FontLoader() override { } +void FontLoader::resource_did_fail() +{ + if (m_on_fail) { + m_on_fail(); + } +} - Vector const& unicode_ranges() const { return m_unicode_ranges; } +RefPtr FontLoader::font_with_point_size(float point_size) +{ + if (!m_vector_font) { + start_loading_next_url(); + return nullptr; + } + return m_vector_font->scaled_font(point_size); +} - virtual void resource_did_load() override - { - auto result = try_load_font(); - if (result.is_error()) { - dbgln("Failed to parse font: {}", result.error()); - start_loading_next_url(); - return; +void FontLoader::start_loading_next_url() +{ + if (resource() && resource()->is_pending()) + return; + if (m_urls.is_empty()) + return; + LoadRequest request; + request.set_url(m_urls.take_first()); + + // HACK: We're crudely computing the referer value and shoving it into the + // request until fetch infrastructure is used here. + auto referrer_url = ReferrerPolicy::strip_url_for_use_as_referrer(m_style_computer.document().url()); + if (referrer_url.has_value() && !request.headers().contains("Referer")) + request.set_header("Referer", referrer_url->serialize()); + + set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); +} + +ErrorOr> FontLoader::try_load_font() +{ + // 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 const& mime_type = resource()->mime_type(); + if (mime_type == "font/ttf"sv || mime_type == "application/x-font-ttf"sv) { + if (auto result = OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { + return result; } - m_vector_font = result.release_value(); - m_style_computer.did_load_font(m_family_name); } - - virtual void resource_did_fail() override - { - } - - RefPtr font_with_point_size(float point_size) - { - if (!m_vector_font) { - start_loading_next_url(); - return nullptr; + if (mime_type == "font/woff"sv || mime_type == "application/font-woff"sv) { + if (auto result = WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { + return result; + } + } + if (mime_type == "font/woff2"sv || mime_type == "application/font-woff2"sv) { + if (auto result = WOFF2::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { + return result; } - return m_vector_font->scaled_font(point_size); } -private: - void start_loading_next_url() - { - if (resource() && resource()->is_pending()) - return; - if (m_urls.is_empty()) - return; - LoadRequest request; - request.set_url(m_urls.take_first()); - - // HACK: We're crudely computing the referer value and shoving it into the - // request until fetch infrastructure is used here. - auto referrer_url = ReferrerPolicy::strip_url_for_use_as_referrer(m_style_computer.document().url()); - if (referrer_url.has_value() && !request.headers().contains("Referer")) - request.set_header("Referer", referrer_url->serialize()); - - set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request)); - } - - ErrorOr> try_load_font() - { - // 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 const& mime_type = resource()->mime_type(); - if (mime_type == "font/ttf"sv || mime_type == "application/x-font-ttf"sv) { - if (auto result = OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { - return result; - } - } - if (mime_type == "font/woff"sv || mime_type == "application/font-woff"sv) { - if (auto result = WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { - return result; - } - } - if (mime_type == "font/woff2"sv || mime_type == "application/font-woff2"sv) { - if (auto result = WOFF2::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) { - return result; - } - } - - // We don't have the luxury of knowing the MIME type, so we have to try all formats. - auto ttf = OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); - if (!ttf.is_error()) - return ttf.release_value(); - auto woff = WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); - if (!woff.is_error()) - return woff.release_value(); - auto woff2 = WOFF2::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); - if (!woff2.is_error()) - return woff2.release_value(); - return Error::from_string_literal("Automatic format detection failed"); - } - - StyleComputer& m_style_computer; - FlyString m_family_name; - Vector m_unicode_ranges; - RefPtr m_vector_font; - Vector m_urls; -}; + // We don't have the luxury of knowing the MIME type, so we have to try all formats. + auto ttf = OpenType::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); + if (!ttf.is_error()) + return ttf.release_value(); + auto woff = WOFF::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); + if (!woff.is_error()) + return woff.release_value(); + auto woff2 = WOFF2::Font::try_load_from_externally_owned_memory(resource()->encoded_data()); + if (!woff2.is_error()) + return woff2.release_value(); + return Error::from_string_literal("Automatic format detection failed"); +} struct StyleComputer::MatchingFontCandidate { FontFaceKey key; @@ -2566,40 +2560,54 @@ void StyleComputer::did_load_font(FlyString const&) document().invalidate_style(); } +Optional StyleComputer::load_font_face(ParsedFontFace const& font_face, Function on_load, Function on_fail) +{ + if (font_face.sources().is_empty()) { + if (on_fail) + on_fail(); + return {}; + } + + FontFaceKey key { + .family_name = font_face.font_family(), + .weight = font_face.weight().value_or(0), + .slope = font_face.slope().value_or(0), + }; + + Vector urls; + for (auto const& source : font_face.sources()) { + // FIXME: These should be loaded relative to the stylesheet URL instead of the document URL. + if (source.local_or_url.has()) + urls.append(m_document->parse_url(MUST(source.local_or_url.get().to_string()))); + // FIXME: Handle local() + } + + if (urls.is_empty()) { + if (on_fail) + on_fail(); + return {}; + } + + auto loader = make(const_cast(*this), font_face.font_family(), font_face.unicode_ranges(), move(urls), move(on_load), move(on_fail)); + auto& loader_ref = *loader; + auto maybe_font_loaders_list = const_cast(*this).m_loaded_fonts.get(key); + if (maybe_font_loaders_list.has_value()) { + maybe_font_loaders_list->append(move(loader)); + } else { + FontLoaderList loaders; + loaders.append(move(loader)); + const_cast(*this).m_loaded_fonts.set(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 const& sheet) { - for (auto const& rule : static_cast(sheet).rules()) { + for (auto const& rule : sheet.rules()) { if (!is(*rule)) continue; - auto const& font_face = static_cast(*rule).font_face(); - if (font_face.sources().is_empty()) - continue; - FontFaceKey key { - .family_name = font_face.font_family(), - .weight = font_face.weight().value_or(0), - .slope = font_face.slope().value_or(0), - }; - - Vector urls; - for (auto& source : font_face.sources()) { - // FIXME: These should be loaded relative to the stylesheet URL instead of the document URL. - if (source.local_or_url.has()) - urls.append(m_document->parse_url(MUST(source.local_or_url.get().to_string()))); - // FIXME: Handle local() - } - - if (urls.is_empty()) - continue; - - auto loader = make(const_cast(*this), font_face.font_family(), font_face.unicode_ranges(), move(urls)); - auto maybe_font_loaders_list = const_cast(*this).m_loaded_fonts.get(key); - if (maybe_font_loaders_list.has_value()) { - maybe_font_loaders_list->append(move(loader)); - } else { - FontLoaderList loaders; - loaders.append(move(loader)); - const_cast(*this).m_loaded_fonts.set(key, move(loaders)); - } + (void)load_font_face(static_cast(*rule).font_face()); } } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index 35cf8fd1eb7..359308fa610 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace Web::CSS { @@ -102,6 +103,8 @@ struct FontFaceKey { [[nodiscard]] bool operator==(FontFaceKey const&) const = default; }; +class FontLoader; + class StyleComputer { public: enum class AllowUnresolved { @@ -135,6 +138,8 @@ public: void did_load_font(FlyString const& family_name); + Optional load_font_face(ParsedFontFace const&, Function on_load = {}, Function on_fail = {}); + void load_fonts_from_sheet(CSSStyleSheet const&); RefPtr compute_font_for_style_values(DOM::Element const* element, Optional pseudo_element, StyleValue const& font_family, StyleValue const& font_size, StyleValue const& font_style, StyleValue const& font_weight, StyleValue const& font_stretch, int math_depth = 0) const; @@ -153,7 +158,6 @@ private: CreatePseudoElementStyleIfNeeded, }; - class FontLoader; struct MatchingFontCandidate; [[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const; @@ -226,4 +230,31 @@ private: CountingBloomFilter m_ancestor_filter; }; +class FontLoader : public ResourceClient { +public: + FontLoader(StyleComputer& style_computer, FlyString family_name, Vector unicode_ranges, Vector urls, Function on_load = {}, Function on_fail = {}); + + virtual ~FontLoader() override; + + Vector const& unicode_ranges() const { return m_unicode_ranges; } + RefPtr vector_font() const { return m_vector_font; } + + virtual void resource_did_load() override; + virtual void resource_did_fail() override; + + RefPtr font_with_point_size(float point_size); + void start_loading_next_url(); + +private: + ErrorOr> try_load_font(); + + StyleComputer& m_style_computer; + FlyString m_family_name; + Vector m_unicode_ranges; + RefPtr m_vector_font; + Vector m_urls; + Function m_on_load; + Function m_on_fail; +}; + }