/* * Copyright (c) 2023-2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace Gfx { class FontCascadeList : public RefCounted { public: using SystemFontFallbackCallback = Function(u32, Font const&)>; static NonnullRefPtr create() { return adopt_ref(*new FontCascadeList()); } size_t size() const { return m_fonts.size(); } bool is_empty() const { return m_fonts.is_empty() && m_pending_faces.is_empty() && !m_last_resort_font; } Font const& first() const { return !m_fonts.is_empty() ? *m_fonts.first().font : *m_last_resort_font; } template void for_each_font_entry(Callback callback) const { for (auto const& font : m_fonts) callback(font); } void add(NonnullRefPtr font); void add(NonnullRefPtr font, Vector unicode_ranges); // Register an unloaded face covering `unicode_ranges`. The cascade invokes // `start_load` the first time a rendered codepoint falls within one of the ranges. void add_pending_face(Vector unicode_ranges, Function start_load); void extend(FontCascadeList const& other); Gfx::Font const& font_for_code_point(u32 code_point) const; bool equals(FontCascadeList const& other) const; struct Entry { NonnullRefPtr font; struct RangeData { // The enclosing range is the union of all Unicode ranges. Used for fast skipping. UnicodeRange enclosing_range; Vector unicode_ranges; }; Optional range_data; }; class PendingFace : public RefCounted { public: PendingFace(UnicodeRange enclosing, Vector ranges, Function start_load) : m_enclosing_range(enclosing) , m_unicode_ranges(move(ranges)) , m_start_load(move(start_load)) { } bool covers(u32 code_point) const { if (!m_enclosing_range.contains(code_point)) return false; for (auto const& range : m_unicode_ranges) { if (range.contains(code_point)) return true; } return false; } void start_load() { m_start_load(); } private: UnicodeRange m_enclosing_range; Vector m_unicode_ranges; Function m_start_load; }; void set_last_resort_font(NonnullRefPtr font) { m_last_resort_font = move(font); } void set_system_font_fallback_callback(SystemFontFallbackCallback callback) { m_system_font_fallback_callback = move(callback); } Font const& first_text_face() const { for (auto const& entry : m_fonts) if (!entry.font->is_emoji_font()) return *entry.font; return first(); } private: RefPtr m_last_resort_font; mutable Vector m_fonts; mutable Vector> m_pending_faces; SystemFontFallbackCallback m_system_font_fallback_callback; // OPTIMIZATION: Cache of resolved fonts for ASCII code points. Since m_fonts only grows and the cascade returns // the first matching font, a cached hit can never become stale. mutable Array m_ascii_cache {}; }; }