Files
ladybird/Libraries/LibWeb/CSS/FontComputer.h
Tim Ledbetter 5d69c6d2b7 LibWeb: Filter by font width before weight in font matching
Implement the width filtering step of the font matching algorithm.
Without it, system font providers that group all widths under one
family could return a condensed variant for font-width: normal,
producing visibly narrower text.
2026-04-24 20:19:38 +02:00

148 lines
6.4 KiB
C++

/*
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <LibGC/CellAllocator.h>
#include <LibGfx/FontCascadeList.h>
#include <LibWeb/CSS/Fetch.h>
#include <LibWeb/CSS/FontFeatureData.h>
#include <LibWeb/CSS/Percentage.h>
#include <LibWeb/CSS/StyleValues/StyleValue.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/PixelUnits.h>
namespace Web::CSS {
struct FontWeightRange {
int min { 0 };
int max { 0 };
[[nodiscard]] u32 hash() const { return pair_int_hash(min, max); }
[[nodiscard]] bool operator==(FontWeightRange const&) const = default;
[[nodiscard]] bool contains_inclusive(int weight) const { return min <= weight && weight <= max; }
};
struct FontFaceKey {
FlyString family_name;
FontWeightRange weight;
int slope { 0 };
int width { 100 };
[[nodiscard]] u32 hash() const { return pair_int_hash(family_name.ascii_case_insensitive_hash(), pair_int_hash(weight.hash(), pair_int_hash(slope, width))); }
[[nodiscard]] bool operator==(FontFaceKey const& other) const
{
return family_name.equals_ignoring_ascii_case(other.family_name)
&& weight == other.weight
&& slope == other.slope
&& width == other.width;
}
};
struct ComputedFontCacheKey {
ValueComparingNonnullRefPtr<StyleValue const> font_family;
FontOpticalSizing font_optical_sizing;
CSSPixels font_size;
int font_slope;
double font_weight;
Percentage font_width;
HashMap<FlyString, double> font_variation_settings;
FontFeatureData font_feature_data;
[[nodiscard]] bool operator==(ComputedFontCacheKey const& other) const = default;
};
class FontLoader final : public GC::Cell {
GC_CELL(FontLoader, GC::Cell);
GC_DECLARE_ALLOCATOR(FontLoader);
public:
FontLoader(FontComputer&, RuleOrDeclaration, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL> urls, GC::Ptr<GC::Function<void(RefPtr<Gfx::Typeface const>)>> on_load = {});
virtual ~FontLoader();
Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
RefPtr<Gfx::Font const> font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations, Gfx::ShapeFeatures const& shape_features);
void start_loading_next_url();
bool is_loading() const;
FlyString family_name() const { return m_family_name; }
private:
virtual void visit_edges(Visitor&) override;
Optional<ByteString> try_load_font_mime_type_essence(Fetch::Infrastructure::Response const&, ByteBuffer const&);
void font_did_load_or_fail(RefPtr<Gfx::Typeface const>);
GC::Ref<FontComputer> m_font_computer;
RuleOrDeclaration m_rule_or_declaration;
FlyString m_family_name;
Vector<Gfx::UnicodeRange> m_unicode_ranges;
RefPtr<Gfx::Typeface const> m_typeface;
Vector<URL> m_urls;
GC::Ptr<Fetch::Infrastructure::FetchController> m_fetch_controller;
GC::Ptr<GC::Function<void(RefPtr<Gfx::Typeface const>)>> 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 clear_computed_font_cache(FlyString const& family_name);
void clear_font_feature_values_cache(FlyString const& family_name);
void did_load_font(FlyString const& family_name);
void register_font_face(GC::Ref<FontFace>);
void unregister_font_face(GC::Ref<FontFace>);
GC::Ptr<FontLoader> load_font_face(ParsedFontFace const&, GC::Ptr<GC::Function<void(RefPtr<Gfx::Typeface const>)>> on_load = {});
void load_fonts_from_sheet(CSSStyleSheet&);
void unload_fonts_from_sheet(CSSStyleSheet&);
NonnullRefPtr<Gfx::FontCascadeList const> compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int font_slope, double font_weight, Percentage const& font_width, FontOpticalSizing font_optical_sizing, HashMap<FlyString, double> const& font_variation_settings, FontFeatureData const& font_feature_data) const;
private:
virtual void visit_edges(Visitor&) override;
struct MatchingFontCandidate;
RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap<FontFeatureValueKey, Vector<u32>> const& font_feature_values, bool inclusive) const;
RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap<FontFeatureValueKey, Vector<u32>> const& font_feature_values, bool inclusive) const;
NonnullRefPtr<Gfx::FontCascadeList const> compute_font_for_style_values_impl(StyleValue const& font_family, CSSPixels const& font_size, int font_slope, double font_weight, Percentage const& font_width, FontOpticalSizing font_optical_sizing, HashMap<FlyString, double> const& font_variation_settings, FontFeatureData const& font_feature_data) const;
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, Percentage const& font_width, int slope, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap<FontFeatureValueKey, Vector<u32>> const& font_feature_values) const;
HashMap<FontFeatureValueKey, Vector<u32>> const& font_feature_values_for_family(FlyString const& family_name) const;
GC::Ref<DOM::Document> m_document;
HashMap<FontFaceKey, Vector<GC::Ref<FontFace>>> m_font_faces;
mutable HashMap<ComputedFontCacheKey, NonnullRefPtr<Gfx::FontCascadeList const>> m_computed_font_cache;
mutable HashMap<FlyString, HashMap<FontFeatureValueKey, Vector<u32>>> m_font_feature_values_cache;
};
}