Skip to content

Commit 195116b

Browse files
authored
Merge pull request #6598 from frenzibyte/fix-text-builder
Preserve weight and italics when falling back to other fonts in text rendering
2 parents 02f1cc1 + 91a13a8 commit 195116b

File tree

5 files changed

+94
-23
lines changed

5 files changed

+94
-23
lines changed

osu.Framework.Tests/Text/TextBuilderTest.cs

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -559,17 +559,61 @@ public void TestSameCharacterFallsBackWithNoFontName()
559559
var font = new FontUsage("test", size: font_size);
560560
var nullFont = new FontUsage(null);
561561
var builder = new TextBuilder(new TestStore(
562-
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
563-
new GlyphEntry(nullFont, new TestGlyph('a', 0, 0, 0, 0, 0, 0, 0)),
564-
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0, 0)),
565-
new GlyphEntry(nullFont, new TestGlyph('?', 0, 0, 0, 0, 0, 0, 0))
562+
new GlyphEntry(font, new TestGlyph('b')),
563+
new GlyphEntry(nullFont, new TestGlyph('a')),
564+
new GlyphEntry(font, new TestGlyph('?')),
565+
new GlyphEntry(nullFont, new TestGlyph('?'))
566566
), font);
567567

568568
builder.AddText("a");
569569

570570
Assert.That(builder.Characters[0].Character, Is.EqualTo('a'));
571571
}
572572

573+
/// <summary>
574+
/// Tests that glyph lookup falls back to using the same character with a different font but same weight.
575+
/// </summary>
576+
[Test]
577+
public void TestSameCharacterFallsBackToDifferentFontWithSameWeight()
578+
{
579+
var font1B = new FontUsage("test1", weight: "Bold", size: font_size);
580+
var font2B = new FontUsage("test2", weight: "Bold", size: font_size);
581+
var font2R = new FontUsage("test2", weight: "Regular", size: font_size);
582+
583+
var builder = new TextBuilder(new TestStore(
584+
new GlyphEntry(font1B, new TestGlyph('b', fontName: font1B.FontName)),
585+
new GlyphEntry(font2R, new TestGlyph('a', fontName: font2R.FontName)),
586+
new GlyphEntry(font2B, new TestGlyph('a', fontName: font2B.FontName))
587+
), font1B);
588+
589+
builder.AddText("a");
590+
591+
Assert.That(builder.Characters[0].Character, Is.EqualTo('a'));
592+
Assert.That(((TestGlyph)builder.Characters[0].Glyph).FontName, Is.EqualTo("test2-Bold"));
593+
}
594+
595+
/// <summary>
596+
/// Tests that glyph lookup falls back to using the same character with a different font but italics are preserved.
597+
/// </summary>
598+
[Test]
599+
public void TestSameCharacterFallsBackToDifferentFontWithItalics()
600+
{
601+
var font1I = new FontUsage("test1", italics: true, size: font_size);
602+
var font2I = new FontUsage("test2", italics: true, size: font_size);
603+
var font2R = new FontUsage("test2", italics: false, size: font_size);
604+
605+
var builder = new TextBuilder(new TestStore(
606+
new GlyphEntry(font1I, new TestGlyph('b', fontName: font1I.FontName)),
607+
new GlyphEntry(font2R, new TestGlyph('a', fontName: font2R.FontName)),
608+
new GlyphEntry(font2I, new TestGlyph('a', fontName: font2I.FontName))
609+
), font1I);
610+
611+
builder.AddText("a");
612+
613+
Assert.That(builder.Characters[0].Character, Is.EqualTo('a'));
614+
Assert.That(((TestGlyph)builder.Characters[0].Glyph).FontName, Is.EqualTo("test2-Italic"));
615+
}
616+
573617
/// <summary>
574618
/// Tests that glyph lookup falls back to using the fallback character with the provided font name.
575619
/// </summary>
@@ -579,10 +623,10 @@ public void TestFallBackCharacterFallsBackWithFontName()
579623
var font = new FontUsage("test", size: font_size);
580624
var nullFont = new FontUsage(null);
581625
var builder = new TextBuilder(new TestStore(
582-
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
583-
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
584-
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0, 0)),
585-
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0, 0))
626+
new GlyphEntry(font, new TestGlyph('b')),
627+
new GlyphEntry(nullFont, new TestGlyph('b')),
628+
new GlyphEntry(font, new TestGlyph('?')),
629+
new GlyphEntry(nullFont, new TestGlyph('?', 1))
586630
), font);
587631

588632
builder.AddText("a");
@@ -600,10 +644,10 @@ public void TestFallBackCharacterFallsBackWithNoFontName()
600644
var font = new FontUsage("test", size: font_size);
601645
var nullFont = new FontUsage(null);
602646
var builder = new TextBuilder(new TestStore(
603-
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
604-
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
605-
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0, 0)),
606-
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0, 0))
647+
new GlyphEntry(font, new TestGlyph('b')),
648+
new GlyphEntry(nullFont, new TestGlyph('b')),
649+
new GlyphEntry(font, new TestGlyph('b')),
650+
new GlyphEntry(nullFont, new TestGlyph('?', 1))
607651
), font);
608652

609653
builder.AddText("a");
@@ -653,7 +697,7 @@ public ITexturedCharacterGlyph Get(string? fontName, char character)
653697
if (string.IsNullOrEmpty(fontName))
654698
return glyphs.FirstOrDefault(g => g.Glyph.Character == character).Glyph;
655699

656-
return glyphs.FirstOrDefault(g => g.Font.FontName == fontName && g.Glyph.Character == character).Glyph;
700+
return glyphs.FirstOrDefault(g => g.Font.FontName.EndsWith(fontName, StringComparison.Ordinal) && g.Glyph.Character == character).Glyph;
657701
}
658702

659703
public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => throw new NotImplementedException();
@@ -681,10 +725,11 @@ public GlyphEntry(FontUsage font, ITexturedCharacterGlyph glyph)
681725
public float Baseline { get; }
682726
public float Height { get; }
683727
public char Character { get; }
728+
public string? FontName { get; }
684729

685730
private readonly float glyphKerning;
686731

687-
public TestGlyph(char character, float xOffset, float yOffset, float xAdvance, float width, float baseline, float height, float kerning)
732+
public TestGlyph(char character, float xOffset = 0, float yOffset = 0, float xAdvance = 0, float width = 0, float baseline = 0, float height = 0, float kerning = 0, string? fontName = null)
688733
{
689734
glyphKerning = kerning;
690735
Character = character;
@@ -694,6 +739,7 @@ public TestGlyph(char character, float xOffset, float yOffset, float xAdvance, f
694739
Width = width;
695740
Baseline = baseline;
696741
Height = height;
742+
FontName = fontName;
697743
}
698744

699745
public float GetKerning<T>(T lastGlyph)

osu.Framework/Graphics/Sprites/FontUsage.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ namespace osu.Framework.Graphics.Sprites
5959
[NotNull]
6060
public string FontName { get; }
6161

62+
/// <summary>
63+
/// The part of <see cref="FontName"/> directly after <see cref="Family"/>,
64+
/// used for looking up other font families sharing same weight/italics specifications.
65+
/// </summary>
66+
/// <remarks>
67+
/// The format is of the form: <br />
68+
/// (empty) <br />
69+
/// -Italic <br />
70+
/// -{Weight}Italic
71+
/// </remarks>
72+
[NotNull]
73+
public string FontNameNoFamily { get; }
74+
6275
/// <summary>
6376
/// Creates an instance of <see cref="FontUsage"/> using the specified font <paramref name="family"/>, font <paramref name="weight"/> and a value indicating whether the used font is italic or not.
6477
/// </summary>
@@ -76,13 +89,16 @@ public FontUsage([CanBeNull] string family = null, float size = default_text_siz
7689
FixedWidth = fixedWidth;
7790

7891
FontName = Family + "-";
92+
7993
if (!string.IsNullOrEmpty(weight))
8094
FontName += weight;
8195

8296
if (italics)
8397
FontName += "Italic";
8498

8599
FontName = FontName.TrimEnd('-');
100+
101+
FontNameNoFamily = string.IsNullOrEmpty(Family) ? FontName : FontName.Substring(Family.Length);
86102
}
87103

88104
/// <summary>

osu.Framework/IO/Stores/FontStore.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#nullable disable
55

6+
using System;
67
using System.Collections.Concurrent;
78
using System.Collections.Generic;
89
using System.Threading.Tasks;
@@ -144,12 +145,13 @@ public ITexturedCharacterGlyph Get(string fontName, char character)
144145
if (namespacedGlyphCache.TryGetValue(key, out var existing))
145146
return existing;
146147

147-
string textureName = string.IsNullOrEmpty(fontName) ? character.ToString() : $"{fontName}/{character}";
148-
149148
foreach (var store in glyphStores)
150149
{
151-
if ((string.IsNullOrEmpty(fontName) || fontName == store.FontName) && store.HasGlyph(character))
150+
if (store.FontName.EndsWith(fontName ?? string.Empty, StringComparison.Ordinal) && store.HasGlyph(character))
151+
{
152+
string textureName = $"{store.FontName}/{character}";
152153
return namespacedGlyphCache[key] = new TexturedCharacterGlyph(store.Get(character).AsNonNull(), Get(textureName), 1 / ScaleAdjust);
154+
}
153155
}
154156

155157
foreach (var store in nestedFontStores)

osu.Framework/Text/ITexturedGlyphLookupStore.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ public interface ITexturedGlyphLookupStore
1010
/// <summary>
1111
/// Retrieves a glyph from the store.
1212
/// </summary>
13-
/// <param name="fontName">The name of the font.</param>
13+
/// <param name="fontName">The name of the font. Alternatively, a suffix of the name can be provided to search for the nearest font matching the given suffix.
14+
/// This is used to look up a glyph in any font while requiring certain weight / italics specifications.</param>
1415
/// <param name="character">The character to retrieve.</param>
1516
/// <returns>The character glyph.</returns>
1617
ITexturedCharacterGlyph? Get(string? fontName, char character);
1718

1819
/// <summary>
1920
/// Retrieves a glyph from the store asynchronously.
2021
/// </summary>
21-
/// <param name="fontName">The name of the font.</param>
22+
/// <param name="fontName">The name of the font. Alternatively, a suffix of the name can be provided to search for the nearest font matching the given suffix.
23+
/// This is used to look up a glyph in any font while requiring certain weight / italics specifications.</param>
2224
/// <param name="character">The character to retrieve.</param>
2325
/// <returns>The character glyph.</returns>
2426
Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character);

osu.Framework/Text/TextBuilder.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,15 @@ private bool tryCreateGlyph(char character, out TextBuilderGlyph glyph)
353353

354354
private ITexturedCharacterGlyph? getTexturedGlyph(char character)
355355
{
356-
return store.Get(font.FontName, character)
357-
?? store.Get(string.Empty, character)
358-
?? store.Get(font.FontName, fallbackCharacter)
359-
?? store.Get(string.Empty, fallbackCharacter);
356+
return tryGetGlyph(character, font, store) ??
357+
tryGetGlyph(fallbackCharacter, font, store);
358+
359+
static ITexturedCharacterGlyph? tryGetGlyph(char character, FontUsage font, ITexturedGlyphLookupStore store)
360+
{
361+
return store.Get(font.FontName, character)
362+
?? store.Get(font.FontNameNoFamily, character)
363+
?? store.Get(string.Empty, character);
364+
}
360365
}
361366
}
362367
}

0 commit comments

Comments
 (0)