osu-framework
                                
                                 osu-framework copied to clipboard
                                
                                    osu-framework copied to clipboard
                            
                            
                            
                        Implement CSS-style font size algorithm
- Closes https://github.com/ppy/osu-framework/issues/3271
- Resolves the font size discrepancy in https://github.com/ppy/osu/issues/17940
Adds support for CSS-style font sizing, allowing for our sprite text sizes to match 1:1 with CSS.
As a starting point, the option is disabled by default to avoid breaking every existing sprite text in all games, but as we move forward (with migrating to new designs in osu!), we'll want to make the option enabled by default, and potentially remove the option (keeping it only based on whether the font's metrics are available).
For reviewing ease, I'll recommend going commit-by-commit as the overall diff is a bit messy to read through (mainly because of the addition of FontMetrics, addition of FontUsage.CssScaling, and addition of note font for testing purposes).
FontMetrics
Since the font binaries don't store metadata about the metrics (ascent, descent, em size), it is required for osu! and all consumers of the framework to manually extract them from the font and specify them in AddFont, similar to how it's done here:
@@ -161,19 +162,24 @@ private void load(FrameworkConfigManager config)
             // note that currently this means there could be two async font load operations.
             Fonts.AddStore(localFonts = new FontStore(useAtlas: false));
+            // Roboto and Font Awesome have different metrics for Windows and macOS.
+            // The ones used for Windows are used here for the time being.
+            var robotoMetrics = new FontMetrics(ascent: 1946, descent: 512, emSize: 2048);
+            var fontAwesomeMetrics = new FontMetrics(ascent: 460, descent: 84, emSize: 512);
+
             // Roboto (FrameworkFont.Regular)
-            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-Regular");
-            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-RegularItalic");
-            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-Bold");
-            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-BoldItalic");
+            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-Regular", robotoMetrics);
+            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-RegularItalic", robotoMetrics);
+            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-Bold", robotoMetrics);
+            addFont(localFonts, Resources, @"Fonts/Roboto/Roboto-BoldItalic", robotoMetrics);
             // RobotoCondensed (FrameworkFont.Condensed)
-            addFont(localFonts, Resources, @"Fonts/RobotoCondensed/RobotoCondensed-Regular");
-            addFont(localFonts, Resources, @"Fonts/RobotoCondensed/RobotoCondensed-Bold");
+            addFont(localFonts, Resources, @"Fonts/RobotoCondensed/RobotoCondensed-Regular", robotoMetrics);
+            addFont(localFonts, Resources, @"Fonts/RobotoCondensed/RobotoCondensed-Bold", robotoMetrics);
-            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Solid");
-            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Regular");
-            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Brands");
+            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Solid", fontAwesomeMetrics);
+            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Regular", fontAwesomeMetrics);
+            addFont(Fonts, Resources, @"Fonts/FontAwesome5/FontAwesome-Brands", fontAwesomeMetrics);
             dependencies.Cache(Fonts);
- 
Note that different platforms use different metric tables, and I'm not entirely sure if we should use each metric based on the platform, as it turns out there can be differences. Therefore I continued with the one used by Windows. This is still completely up to the consumer whether they want it to be platform-specific or not, but just mentioning this in regards to osu!. 
- 
I've kept the font metrics argument in AddFontas optional, for special fonts which don't have the concept of "font metrics" ("8-bit" fonts and the like).
I will write up more about this in the fonts wiki if the FontMetrics concept is agreed on, mainly about how to extract them and which values should be used, etc.
FontUsage.CssScaling
Going forward, osu! and all consumers should always append css: true to their font usages, potentially using a different static and prefixing the old non-CSS ones with Old:
diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs
index edb484021c..62eb7492f8 100644
--- a/osu.Game/Graphics/OsuFont.cs
+++ b/osu.Game/Graphics/OsuFont.cs
@@ -19,7 +19,9 @@ public static class OsuFont
         public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold);
-        public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
+        public static FontUsage OldTorus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
+
+        public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular, css: true);
         public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular);
It could also go the other way around, by keeping the old one as-is, and defining a New font with css: true applied instead.
I've added behavioural test coverage, and also added a visual illustration in the sprite text test scene, by comparing against a picture of a CSS text element with an equal font size, in a properly aligned container:
 
Have also added sprite texts with CSS-style enabled, alongside the pre-existing non-CSS ones:
 
                                    
                                    
                                    
                                
and addition of noto font for testing purposes
Why was this done? I think it would have sufficed to test Roboto.
Just for the sake of adding more into the visualisation test, and also to confirm that the framework’s handling of multiple fonts in one sprite text also matches CSS.
The test doesn't seem to be working for me:

I don't seem to have any local changes, so hopefully it's not just me?
Indeed it regressed, will look into this immediately...
Apparently the test scene regressed because of https://github.com/ppy/osu-framework/pull/5123/commits/78d32b636864d29903c976fb319fda4b2f3c80e9 and I swear it wasn't broken to me the moment I tested it...
That commit was changing the metrics values to use the ones which Windows respects (typo), but the text images provided were taken on macOS (hhea), therefore causing this discrepancy.
The metrics provided by Windows lead to CSS scale of 1x ((880 + 120) / 1000 = 1), so I'm going to stick with the macOS ones with an inline comment and call it a day.
That commit was changing the metrics values to use the ones which Windows respects (typo), but the text images provided were taken on macOS (hhea)
Well this is disconcerting, are you saying this can differ per-platform somehow, too? I would hope not...
Well, some fonts render differently on browsers in Windows and macOS, due to there existing different metrics tables (most of the time the same values will be used on all metrics, but some fonts like Roboto and Noto Sans JP don't):
- typo, which Windows browsers uses.
- hhea, which macOS browsers uses.
- win, which... Windows browsers uses as well?
It is up for the framework consumer to decide whether they want their font to be rendered correctly based on the running platform (using RuntimeInfo.OS), or by sticking to one metrics table and call it a day.
For osu!, I think it may be best sticking to the Typo table, but we can consider rendering per-platform correctly if we want to. Just a matter of supplying the font with the correct metric values.
I think I'm just going to pretend I didn't ask and nod along. This is a rabbit hole the depths of which I do not dare to explore.
For reference I found the relevant platform specific hacks that Chrome uses: https://github.com/chromium/chromium/blob/76769a9d7e911b4efa1fab3cc6e1b5df86f3d227/third_party/blink/renderer/platform/fonts/simple_font_data.cc#L88-L179