diff --git a/Libraries/LibWeb/CSS/FontComputer.cpp b/Libraries/LibWeb/CSS/FontComputer.cpp index 89a0faa9577..84354531c2b 100644 --- a/Libraries/LibWeb/CSS/FontComputer.cpp +++ b/Libraries/LibWeb/CSS/FontComputer.cpp @@ -9,6 +9,7 @@ */ #include "FontComputer.h" +#include #include #include #include @@ -215,8 +216,40 @@ Optional FontLoader::try_load_font_mime_type_essence(Fetch::Infrastr return mime_type->essence().to_byte_string(); } +static unsigned font_width_bucket_from_percentage(double percentage) +{ + // Maps a font-width Percentage to the nearest standard Gfx::FontWidth bucket. + + struct Bucket { + double percentage; + unsigned width; + }; + static constexpr Array buckets = { { + { 50.0, Gfx::FontWidth::UltraCondensed }, + { 62.5, Gfx::FontWidth::ExtraCondensed }, + { 75.0, Gfx::FontWidth::Condensed }, + { 87.5, Gfx::FontWidth::SemiCondensed }, + { 100.0, Gfx::FontWidth::Normal }, + { 112.5, Gfx::FontWidth::SemiExpanded }, + { 125.0, Gfx::FontWidth::Expanded }, + { 150.0, Gfx::FontWidth::ExtraExpanded }, + { 200.0, Gfx::FontWidth::UltraExpanded }, + } }; + auto best = buckets[0]; + auto best_distance = AK::fabs(percentage - best.percentage); + for (size_t i = 1; i < buckets.size(); ++i) { + auto distance = AK::fabs(percentage - buckets[i].percentage); + if (distance < best_distance) { + best_distance = distance; + best = buckets[i]; + } + } + return best.width; +} + struct FontComputer::MatchingFontCandidate { FontFaceKey key; + unsigned width { Gfx::FontWidth::Normal }; Gfx::Typeface const* system_typeface { nullptr }; [[nodiscard]] RefPtr font_with_point_size(HashMap>> const& font_faces, float point_size, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap> const& font_feature_values) const @@ -280,15 +313,17 @@ RefPtr FontComputer::find_matching_font_weight_desce // 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(FlyString const& family_name, int weight, int slope, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap> const& font_feature_values) const +RefPtr FontComputer::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> const& font_feature_values) 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; // FIXME: URL-backed faces with no typeface yet should trigger a load on demand, matching other engines. for (auto const& [map_key, faces] : m_font_faces) { - if (map_key.family_name.equals_ignoring_ascii_case(family_name)) + if (map_key.family_name.equals_ignoring_ascii_case(family_name)) { matching_family_fonts.empend(map_key); + matching_family_fonts.last().width = font_width_bucket_from_percentage(map_key.width); + } } Gfx::FontDatabase::the().for_each_typeface_with_family_name(family_name, [&](Gfx::Typeface const& typeface) { matching_family_fonts.append({ @@ -298,6 +333,7 @@ RefPtr FontComputer::font_matching_algorithm(FlyStri .weight = { static_cast(typeface.weight()), static_cast(typeface.weight()) }, .slope = typeface.slope(), }, + .width = typeface.width(), .system_typeface = &typeface, }); }); @@ -305,11 +341,20 @@ RefPtr FontComputer::font_matching_algorithm(FlyStri if (matching_family_fonts.is_empty()) return {}; + // 1. font-width is tried first. + auto desired_width = font_width_bucket_from_percentage(font_width.value()); + auto width_it = find_if(matching_family_fonts.begin(), matching_family_fonts.end(), + [&](auto const& matching_font_candidate) { return matching_font_candidate.width == desired_width; }); + if (width_it != matching_family_fonts.end()) { + matching_family_fonts.remove_all_matching([&](auto const& matching_font_candidate) { + return matching_font_candidate.width != desired_width; + }); + } + quick_sort(matching_family_fonts, [](auto const& a, auto const& b) { return a.key.weight.min < b.key.weight.min; }); - // FIXME: 1. font-width is tried first. - // FIXME: 2. font-style is tried next. + // 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(), @@ -478,7 +523,7 @@ NonnullRefPtr FontComputer::compute_font_for_style_v return result; } - if (auto found_font = font_matching_algorithm(family, weight, slope, font_size_in_pt, variation, font_feature_data, font_feature_values); found_font && !found_font->is_empty()) + if (auto found_font = font_matching_algorithm(family, weight, font_width, slope, font_size_in_pt, variation, font_feature_data, font_feature_values); found_font && !found_font->is_empty()) return found_font; return {}; diff --git a/Libraries/LibWeb/CSS/FontComputer.h b/Libraries/LibWeb/CSS/FontComputer.h index 737f543ed17..12ee72cac31 100644 --- a/Libraries/LibWeb/CSS/FontComputer.h +++ b/Libraries/LibWeb/CSS/FontComputer.h @@ -132,7 +132,7 @@ private: RefPtr find_matching_font_weight_ascending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap> const& font_feature_values, bool inclusive) const; RefPtr find_matching_font_weight_descending(Vector const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap> const& font_feature_values, bool inclusive) const; NonnullRefPtr 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 const& font_variation_settings, FontFeatureData const& font_feature_data) const; - RefPtr font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt, Gfx::FontVariationSettings const& variations, FontFeatureData const& font_feature_data, HashMap> const& font_feature_values) const; + RefPtr 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> const& font_feature_values) const; HashMap> const& font_feature_values_for_family(FlyString const& family_name) const; diff --git a/Tests/LibWeb/Text/expected/css/font-face-width-matching.txt b/Tests/LibWeb/Text/expected/css/font-face-width-matching.txt new file mode 100644 index 00000000000..0f4a2f90763 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/font-face-width-matching.txt @@ -0,0 +1,3 @@ +reference fonts differ: true +condensed uses HashSans: true +normal uses Lato: true diff --git a/Tests/LibWeb/Text/input/css/font-face-width-matching.html b/Tests/LibWeb/Text/input/css/font-face-width-matching.html new file mode 100644 index 00000000000..c505a937182 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/font-face-width-matching.html @@ -0,0 +1,49 @@ + + +WWWW +WWWW +WWWW +WWWW + +