Skip to content

Commit

Permalink
LibWeb: Expose StyleComputer's FontLoader class publicly
Browse files Browse the repository at this point in the history
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'.
  • Loading branch information
ADKaster authored and awesomekling committed May 16, 2024
1 parent d76167b commit b7526a3
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 114 deletions.
234 changes: 121 additions & 113 deletions Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Platform/FontPlugin.h>
Expand Down Expand Up @@ -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<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls)
: m_style_computer(style_computer)
, m_family_name(move(family_name))
, m_unicode_ranges(move(unicode_ranges))
, m_urls(move(urls))
{
}

virtual ~FontLoader() override { }
FontLoader::FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls, Function<void(FontLoader const&)> on_load, Function<void()> 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))
{
}

Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
FontLoader::~FontLoader() = default;

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;
}
m_vector_font = result.release_value();
m_style_computer.did_load_font(m_family_name);
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 void resource_did_fail() override
{
void FontLoader::resource_did_fail()
{
if (m_on_fail) {
m_on_fail();
}
}

RefPtr<Gfx::Font> 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);
RefPtr<Gfx::Font> 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);
}

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());
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());
// 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));
}
set_resource(ResourceLoader::the().load_resource(Resource::Type::Generic, request));
}

ErrorOr<NonnullRefPtr<Gfx::VectorFont>> 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;
}
ErrorOr<NonnullRefPtr<Gfx::VectorFont>> 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;
}
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/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;
}
}
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<Gfx::UnicodeRange> m_unicode_ranges;
RefPtr<Gfx::VectorFont> m_vector_font;
Vector<URL::URL> 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;
Expand Down Expand Up @@ -2566,40 +2560,54 @@ void StyleComputer::did_load_font(FlyString const&)
document().invalidate_style();
}

void StyleComputer::load_fonts_from_sheet(CSSStyleSheet const& sheet)
Optional<FontLoader&> StyleComputer::load_font_face(ParsedFontFace const& font_face, Function<void(FontLoader const&)> on_load, Function<void()> on_fail)
{
for (auto const& rule : static_cast<CSSStyleSheet const&>(sheet).rules()) {
if (!is<CSSFontFaceRule>(*rule))
continue;
auto const& font_face = static_cast<CSSFontFaceRule const&>(*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),
};
if (font_face.sources().is_empty()) {
if (on_fail)
on_fail();
return {};
}

Vector<URL::URL> 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<URL::URL>())
urls.append(m_document->parse_url(MUST(source.local_or_url.get<URL::URL>().to_string())));
// FIXME: Handle local()
}
FontFaceKey key {
.family_name = font_face.font_family(),
.weight = font_face.weight().value_or(0),
.slope = font_face.slope().value_or(0),
};

if (urls.is_empty())
continue;
Vector<URL::URL> 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<URL::URL>())
urls.append(m_document->parse_url(MUST(source.local_or_url.get<URL::URL>().to_string())));
// FIXME: Handle local()
}

auto loader = make<FontLoader>(const_cast<StyleComputer&>(*this), font_face.font_family(), font_face.unicode_ranges(), move(urls));
auto maybe_font_loaders_list = const_cast<StyleComputer&>(*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<StyleComputer&>(*this).m_loaded_fonts.set(key, move(loaders));
}
if (urls.is_empty()) {
if (on_fail)
on_fail();
return {};
}

auto loader = make<FontLoader>(const_cast<StyleComputer&>(*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<StyleComputer&>(*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<StyleComputer&>(*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 : sheet.rules()) {
if (!is<CSSFontFaceRule>(*rule))
continue;
(void)load_font_face(static_cast<CSSFontFaceRule const&>(*rule).font_face());
}
}

Expand Down
33 changes: 32 additions & 1 deletion Userland/Libraries/LibWeb/CSS/StyleComputer.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleProperties.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Loader/ResourceLoader.h>

namespace Web::CSS {

Expand Down Expand Up @@ -102,6 +103,8 @@ struct FontFaceKey {
[[nodiscard]] bool operator==(FontFaceKey const&) const = default;
};

class FontLoader;

class StyleComputer {
public:
enum class AllowUnresolved {
Expand Down Expand Up @@ -135,6 +138,8 @@ class StyleComputer {

void did_load_font(FlyString const& family_name);

Optional<FontLoader&> load_font_face(ParsedFontFace const&, Function<void(FontLoader const&)> on_load = {}, Function<void()> on_fail = {});

void load_fonts_from_sheet(CSSStyleSheet const&);

RefPtr<Gfx::FontCascadeList const> compute_font_for_style_values(DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> 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;
Expand All @@ -153,7 +158,6 @@ class StyleComputer {
CreatePseudoElementStyleIfNeeded,
};

class FontLoader;
struct MatchingFontCandidate;

[[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const;
Expand Down Expand Up @@ -226,4 +230,31 @@ class StyleComputer {
CountingBloomFilter<u8, 14> m_ancestor_filter;
};

class FontLoader : public ResourceClient {
public:
FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls, Function<void(FontLoader const&)> on_load = {}, Function<void()> on_fail = {});

virtual ~FontLoader() override;

Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
RefPtr<Gfx::VectorFont> vector_font() const { return m_vector_font; }

virtual void resource_did_load() override;
virtual void resource_did_fail() override;

RefPtr<Gfx::Font> font_with_point_size(float point_size);
void start_loading_next_url();

private:
ErrorOr<NonnullRefPtr<Gfx::VectorFont>> try_load_font();

StyleComputer& m_style_computer;
FlyString m_family_name;
Vector<Gfx::UnicodeRange> m_unicode_ranges;
RefPtr<Gfx::VectorFont> m_vector_font;
Vector<URL::URL> m_urls;
Function<void(FontLoader const&)> m_on_load;
Function<void()> m_on_fail;
};

}

0 comments on commit b7526a3

Please sign in to comment.