dotnet / dotnet/maui

[Enhancement] Add an API to handle font families properly

Open
#1,018 5 comments 18 reactions 0 assignees View on GitHub
area-fonts delighter-sc proposal/open t/enhancement ☀️
Dominant language
C#
Stars
23.3k
Forks
2k
Avg merge
1d 15h
Merged PRs (30d)
290

Description

## Summary

Right now, the font system is pretty basic: fonts are registered and then used. This is fine for typical cases where there is just one font, but there are a few platform differences:

**Android**
- all fonts can be bold-ified, even those that are just a "regular" font
- adding a regular and a bold font face doesn't change anything since the regular is always used unless you explicitly load the bold
- a new API was added with Android API 29 to create typefaces from font families: https://developer.android.com/reference/android/graphics/fonts/package-summary
- loading a font by the alias does nothing special and a bold font can be made bold-er

The main reason for the way things are now is that Android does not have a font lookup system and fonts are just loaded via the literal typeface that was created around the data stream.

**iOS**
- the OS is unable to make regular fonts bold, and thus all faces in the family need to be registered
- depending on the families registered, the correct font face is loaded
- loading a bold font via an alias does not do anything and will just use that font

iOS has a font registration system where all fonts to be used are added to the system, and then using the OS-level lookups we get styles.

**Windows**
- like Android, the OS can force styles on any font
- loading a font via an alias is just the same as loading any font - the real family name is read from the font file

## Unknowns
- If there was a way to create font families, how would this be done on Windows. It can be done using font lookup to search for all fonts in a family and then filter based on desired font weights/slants.

## Idea

When registering a font, there should be a way to build up the font family for the OS:

```csharp
builder.ConfigureFonts(fonts => {
fonts.AddFont("standalone.ttf", "Standalone");
fonts.AddFont(new FontFamily {
FamilyName = "Lobster Two",
Typefaces = {
new FontTypeface("regular.ttf", FontWeight.Regular, FontSlant.Default),
new FontTypeface("bold.ttf", FontWeight.Bold, FontSlant.Default),
new FontTypeface("italic.ttf", FontWeight.Regular, FontSlant.Italic),
new FontTypeface("bolditalic.ttf", FontWeight.Bold, FontSlant.Italic),
new FontTypeface("thin.ttf", FontWeight.Thin, FontSlant.Default),
}
});
});
```

For iOS, this doesn't have to do anything, let the OS do the filtering.

For Android, we can use the new font API and fall back to basic checks and select the best match.

For Windows, using custom filtering to select the font as well as influencing the weight/slant properties of the control. For example, setting bold and a non-bold font, we can use the OS bold. But if there is a bold font, then we can use that. It seems that a bold font does not go bolder than the bold.

## Pictures

Looking at the iages below, you can see they are not consistent. There are 5 "fonts", each with a change in weight/slant:

- none: empty string/null
- tnr: Times New Roman (system font that might not actually be available on the OS)
- lobster: the Lobster Two font from Google that has 4 variants (https://fonts.google.com/specimen/Lobster+Two)
- lobster bold: the same Lobster Two font, but this time using the bold alias
- custom: a regular-only font

| Windows | iOS | Android |
| :--: | :--: | :--: |
| ![win](https://user-images.githubusercontent.com/1096616/118056075-864cbe80-b389-11eb-9288-883f801a5a44.png) | ![ios](https://user-images.githubusercontent.com/1096616/118055972-5a313d80-b389-11eb-94df-2dfd8d1ed469.png) | ![android](https://user-images.githubusercontent.com/1096616/118056471-4b975600-b38a-11eb-814e-3fd3b0a7abd4.png) |

```csharp

Content = new VerticalStackLayout
{
new Label { Text = " ", HeightRequest = 40 },
new Label { Text = "none | none", FontAttributes = FontAttributes.None },
new Label { Text = "none | bold", FontAttributes = FontAttributes.Bold },
new Label { Text = "none | italic", FontAttributes = FontAttributes.Italic },
new Label { Text = "none | bolditalic", FontAttributes = FontAttributes.Bold | FontAttributes.Italic },
new Label { Text = "tnr | none", FontFamily = "Times New Roman", FontAttributes = FontAttributes.None },
new Label { Text = "tnr | bold", FontFamily = "Times New Roman", FontAttributes = FontAttributes.Bold },
new Label { Text = "tnr | italic", FontFamily = "Times New Roman", FontAttributes = FontAttributes.Italic },
new Label { Text = "tnr | bolditalic", FontFamily = "Times New Roman", FontAttributes = FontAttributes.Bold | FontAttributes.Italic },
new Label { Text = "lobster | none", FontFamily = "Lobster Two", FontAttributes = FontAttributes.None },
new Label { Text = "lobster | bold", FontFamily = "Lobster Two", FontAttributes = FontAttributes.Bold },
new Label { Text = "lobster | italic", FontFamily = "Lobster Two", FontAttributes = FontAttributes.Italic },
new Label { Text = "lobster | bolditalic", FontFamily = "Lobster Two", FontAttributes = FontAttributes.Bold | FontAttributes.Italic },
new Label { Text = "lobster bold | none", FontFamily = "Lobster Two Bold", FontAttributes = FontAttributes.None },
new Label { Text = "lobster bold | bold", FontFamily = "Lobster Two Bold", FontAttributes = FontAttributes.Bold },
new Label { Text = "lobster bold | italic", FontFamily = "Lobster Two Bold", FontAttributes = FontAttributes.Italic },
new Label { Text = "lobster bold | bolditalic", FontFamily = "Lobster Two Bold", FontAttributes = FontAttributes.Bold | FontAttributes.Italic },
new Label { Text = "custom | none", FontFamily = "Dokdo", FontAttributes = FontAttributes.None },
new Label { Text = "custom | bold", FontFamily = "Dokdo", FontAttributes = FontAttributes.Bold },
new Label { Text = "custom | italic", FontFamily = "Dokdo", FontAttributes = FontAttributes.Italic },
new Label { Text = "custom | bolditalic", FontFamily = "Dokdo", FontAttributes = FontAttributes.Bold | FontAttributes.Italic },
};
```

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the existing font registration and font-loading behavior on Android, iOS, and Windows, then compare how aliases, weights, and slants are handled. Done means an agreed family-based API supports the proposed typeface combinations consistently across the three platforms, including the Windows behavior described as an unknown.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, csharp, ios
Domain
desktop-dev, frontend, mobile-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.