Skip to content

Commit f3a3f56

Browse files
authored
System font API (space-wizards#5393)
* System font API This is a new API that allows operating system fonts to be loaded by the engine and used by content. Fonts are provided in a flat list exposing all the relevant metadata. They are loaded from disk with a Load call. Initial implementation is only for Windows DirectWrite. * Load system fonts as memory mapped files if possible. This allows sharing the font file memory with other processes which is always good. * Use ArrayPool to reduce char array allocations * Disable verbose logging * Implement system font support on Linux via Fontconfig * Implement macOS support * Add "FREEDESKTOP" define constant This is basically LINUX || FREEBSD. Though FreeBSD currently gets detected as LINUX too. Oh well. * Compile out Fontconfig and CoreText system font backends when not on those platforms * Don't add Fontconfig package dep on Mac/Windows * Allow disabling system font support via CVar Cuz why not.
1 parent 8b7fbfa commit f3a3f56

24 files changed

+1841
-12
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<PackageVersion Include="SpaceWizards.Sdl" Version="1.0.0" />
6363
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.1.0" />
6464
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
65+
<PackageVersion Include="SpaceWizards.Fontconfig.Interop" Version="1.0.0" />
6566
<PackageVersion Include="libsodium" Version="1.0.20.1" />
6667
<PackageVersion Include="System.Management" Version="9.0.8" />
6768
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

MSBuild/Robust.DefineConstants.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</When>
1414
<Otherwise>
1515
<PropertyGroup>
16-
<DefineConstants>$(DefineConstants);LINUX;UNIX</DefineConstants>
16+
<DefineConstants>$(DefineConstants);LINUX;UNIX;FREEDESKTOP</DefineConstants>
1717
</PropertyGroup>
1818
</Otherwise>
1919
</Choose>

MSBuild/Robust.Platform.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@
3131
<Python>python3</Python>
3232
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
3333
<UseSystemSqlite Condition="'$(TargetOS)' == 'FreeBSD'">True</UseSystemSqlite>
34+
<IsFreedesktop Condition="'$(TargetOS)' == 'FreeBSD' Or '$(TargetOS)' == 'Linux'">True</IsFreedesktop>
3435
</PropertyGroup>
3536
</Project>

RELEASE-NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ END TEMPLATE-->
4747
* Sandbox:
4848
* Exposed `System.Reflection.Metadata.MetadataUpdateHandlerAttribute`.
4949
* Exposed more overloads on `StringBuilder`.
50+
* The engine can now load system fonts.
51+
* At the moment only available on Windows.
52+
* See `ISystemFontManager` for API.
5053

5154
### Bugfixes
5255

Robust.Client/ClientIoC.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Robust.Client.GameStates;
99
using Robust.Client.Graphics;
1010
using Robust.Client.Graphics.Clyde;
11+
using Robust.Client.Graphics.FontManagement;
1112
using Robust.Client.HWId;
1213
using Robust.Client.Input;
1314
using Robust.Client.Localization;
@@ -121,6 +122,8 @@ public static void RegisterIoC(GameController.DisplayMode mode, IDependencyColle
121122
deps.Register<IInputManager, InputManager>();
122123
deps.Register<IFileDialogManager, DummyFileDialogManager>();
123124
deps.Register<IUriOpener, UriOpenerDummy>();
125+
deps.Register<ISystemFontManager, SystemFontManagerFallback>();
126+
deps.Register<ISystemFontManagerInternal, SystemFontManagerFallback>();
124127
break;
125128
case GameController.DisplayMode.Clyde:
126129
deps.Register<IClyde, Clyde>();
@@ -131,6 +134,8 @@ public static void RegisterIoC(GameController.DisplayMode mode, IDependencyColle
131134
deps.Register<IInputManager, ClydeInputManager>();
132135
deps.Register<IFileDialogManager, FileDialogManager>();
133136
deps.Register<IUriOpener, UriOpener>();
137+
deps.Register<ISystemFontManager, SystemFontManager>();
138+
deps.Register<ISystemFontManagerInternal, SystemFontManager>();
134139
break;
135140
default:
136141
throw new ArgumentOutOfRangeException();

Robust.Client/GameController/GameController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ internal sealed partial class GameController : IGameControllerInternal
9696
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
9797
[Dependency] private readonly IReloadManager _reload = default!;
9898
[Dependency] private readonly ILocalizationManager _loc = default!;
99+
[Dependency] private readonly ISystemFontManagerInternal _systemFontManager = default!;
99100

100101
private IWebViewManagerHook? _webViewHook;
101102

@@ -143,6 +144,7 @@ internal bool StartupContinue(DisplayMode displayMode)
143144
_taskManager.Initialize();
144145
_parallelMgr.Initialize();
145146
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
147+
_systemFontManager.Initialize();
146148

147149
// Load optional Robust modules.
148150
LoadOptionalRobustModules(displayMode, _resourceManifest!);

Robust.Client/Graphics/Font.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ public VectorFont(FontResource res, int size)
104104
Handle = IoCManager.Resolve<IFontManagerInternal>().MakeInstance(res.FontFaceHandle, size);
105105
}
106106

107+
internal VectorFont(IFontInstanceHandle handle, int size)
108+
{
109+
Size = size;
110+
Handle = handle;
111+
}
112+
107113
public override int GetAscent(float scale) => Handle.GetAscent(scale);
108114
public override int GetHeight(float scale) => Handle.GetHeight(scale);
109115
public override int GetDescent(float scale) => Handle.GetDescent(scale);
@@ -222,4 +228,74 @@ public override float DrawChar(DrawingHandleBase handle, Rune rune, Vector2 base
222228
return null;
223229
}
224230
}
231+
232+
/// <summary>
233+
/// Possible values for font weights. Larger values have thicker font strokes.
234+
/// </summary>
235+
/// <remarks>
236+
/// <para>
237+
/// These values are based on the <c>usWeightClass</c> property of the OpenType specification:
238+
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass
239+
/// </para>
240+
/// </remarks>
241+
/// <seealso cref="ISystemFontFace.Weight"/>
242+
public enum FontWeight : ushort
243+
{
244+
Thin = 100,
245+
ExtraLight = 200,
246+
UltraLight = ExtraLight,
247+
Light = 300,
248+
SemiLight = 350,
249+
Normal = 400,
250+
Regular = Normal,
251+
Medium = 500,
252+
SemiBold = 600,
253+
DemiBold = SemiBold,
254+
Bold = 700,
255+
ExtraBold = 800,
256+
UltraBold = ExtraBold,
257+
Black = 900,
258+
Heavy = Black,
259+
ExtraBlack = 950,
260+
UltraBlack = ExtraBlack,
261+
}
262+
263+
/// <summary>
264+
/// Possible slant values for fonts.
265+
/// </summary>
266+
/// <seealso cref="ISystemFontFace.Slant"/>
267+
public enum FontSlant : byte
268+
{
269+
// NOTE: Enum values correspond to DWRITE_FONT_STYLE.
270+
Normal = 0,
271+
Oblique = 1,
272+
273+
// FUN FACT: they're called "italics" because they look like the Leaning Tower of Pisa.
274+
// Don't fact-check that.
275+
Italic = 2
276+
}
277+
278+
/// <summary>
279+
/// Possible values for font widths. Larger values are proportionally wider.
280+
/// </summary>
281+
/// <remarks>
282+
/// <para>
283+
/// These values are based on the <c>usWidthClass</c> property of the OpenType specification:
284+
/// https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass
285+
/// </para>
286+
/// </remarks>
287+
/// <seealso cref="ISystemFontFace.Width"/>
288+
public enum FontWidth : ushort
289+
{
290+
UltraCondensed = 1,
291+
ExtraCondensed = 2,
292+
Condensed = 3,
293+
SemiCondensed = 4,
294+
Normal = 5,
295+
Medium = Normal,
296+
SemiExpanded = 6,
297+
Expanded = 7,
298+
ExtraExpanded = 8,
299+
UltraExpanded = 9,
300+
}
225301
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Robust.Shared.Console;
2+
3+
namespace Robust.Client.Graphics.FontManagement;
4+
5+
internal sealed class SystemFontDebugCommand : IConsoleCommand
6+
{
7+
public string Command => "system_font_debug";
8+
public string Description => "";
9+
public string Help => "";
10+
11+
public void Execute(IConsoleShell shell, string argStr, string[] args)
12+
{
13+
new SystemFontDebugWindow().OpenCentered();
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<DefaultWindow xmlns="https://spacestation14.io"
2+
Title="System font debug">
3+
<SplitContainer Orientation="Horizontal" MinSize="800 600">
4+
<ScrollContainer HScrollEnabled="False">
5+
<BoxContainer Name="SelectorContainer" Orientation="Vertical" />
6+
</ScrollContainer>
7+
<ScrollContainer HScrollEnabled="False">
8+
<BoxContainer Orientation="Vertical">
9+
<Label Name="FamilyLabel" />
10+
<BoxContainer Orientation="Vertical" Name="FaceContainer" />
11+
</BoxContainer>
12+
</ScrollContainer>
13+
</SplitContainer>
14+
</DefaultWindow>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System.Linq;
2+
using Robust.Client.AutoGenerated;
3+
using Robust.Client.UserInterface;
4+
using Robust.Client.UserInterface.Controls;
5+
using Robust.Client.UserInterface.CustomControls;
6+
using Robust.Client.UserInterface.XAML;
7+
using Robust.Shared.IoC;
8+
using Robust.Shared.Maths;
9+
using Robust.Shared.Utility;
10+
11+
namespace Robust.Client.Graphics.FontManagement;
12+
13+
[GenerateTypedNameReferences]
14+
internal sealed partial class SystemFontDebugWindow : DefaultWindow
15+
{
16+
private static readonly int[] ExampleFontSizes = [8, 12, 16, 24, 36];
17+
private const string ExampleString = "The quick brown fox jumps over the lazy dog";
18+
19+
[Dependency] private readonly ISystemFontManager _systemFontManager = default!;
20+
21+
public SystemFontDebugWindow()
22+
{
23+
IoCManager.InjectDependencies(this);
24+
RobustXamlLoader.Load(this);
25+
26+
var buttonGroup = new ButtonGroup();
27+
28+
foreach (var group in _systemFontManager.SystemFontFaces.GroupBy(k => k.FamilyName).OrderBy(k => k.Key))
29+
{
30+
var fonts = group.ToArray();
31+
SelectorContainer.AddChild(new Selector(this, buttonGroup, group.Key, fonts));
32+
}
33+
}
34+
35+
private void SelectFontFamily(ISystemFontFace[] fonts)
36+
{
37+
FamilyLabel.Text = fonts[0].FamilyName;
38+
39+
FaceContainer.RemoveAllChildren();
40+
41+
foreach (var font in fonts)
42+
{
43+
var exampleContainer = new BoxContainer
44+
{
45+
Orientation = BoxContainer.LayoutOrientation.Vertical,
46+
Margin = new Thickness(8)
47+
};
48+
49+
foreach (var size in ExampleFontSizes)
50+
{
51+
var fontInstance = font.Load(size);
52+
53+
var richTextLabel = new RichTextLabel
54+
{
55+
Stylesheet = new Stylesheet([
56+
StylesheetHelpers.Element<RichTextLabel>().Prop("font", fontInstance)
57+
]),
58+
};
59+
richTextLabel.SetMessage(FormattedMessage.FromUnformatted(ExampleString));
60+
exampleContainer.AddChild(richTextLabel);
61+
}
62+
63+
FaceContainer.AddChild(new BoxContainer
64+
{
65+
Orientation = BoxContainer.LayoutOrientation.Vertical,
66+
Children =
67+
{
68+
new RichTextLabel
69+
{
70+
Text = $"""
71+
{font.FullName}
72+
Family: "{font.FamilyName}", face: "{font.FaceName}", PostScript = "{font.PostscriptName}"
73+
Weight: {font.Weight} ({(int) font.Weight}), slant: {font.Slant} ({(int) font.Slant}), width: {font.Width} ({(int) font.Width})
74+
""",
75+
},
76+
exampleContainer
77+
},
78+
Margin = new Thickness(0, 0, 0, 8)
79+
});
80+
}
81+
}
82+
83+
private sealed class Selector : Control
84+
{
85+
public Selector(SystemFontDebugWindow window, ButtonGroup group, string family, ISystemFontFace[] fonts)
86+
{
87+
var button = new Button
88+
{
89+
Text = family,
90+
Group = group,
91+
ToggleMode = true
92+
};
93+
AddChild(button);
94+
95+
button.OnPressed += _ => window.SelectFontFamily(fonts);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)