Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WASM] Font preloading support #9543

Merged
merged 13 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 84 additions & 14 deletions doc/articles/features/custom-fonts.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,44 +41,87 @@ The format is the same as Windows, as follows:
```xml
<Setter Property="FontFamily" Value="/Assets/Fonts/yourfont.ttf#Your Font Name" />
```
or
or

```xml
<Setter Property="FontFamily" Value="ms-appx:///Assets/Fonts/yourfont.ttf#Your Font Name" />
```

## Custom fonts on WebAssembly

Adding a custom font is done through the use of WebFonts, using a data-URI:
There is 3 ways to use fonts on WebAssembly platform:

1. Referencing a **font defined in CSS**: Use a font defined using a `@font-face` CSS clause.

> [!NOTE]
> This was the only available way to define and use a custom font before Uno.UI v4.4. This is useful if the application is using externally referenced CSS as those commonly available on a CDN.

2. Referencing a **font file in application assets**: Use a font file (any web comptatible file format, such as `.ttf`, `.woff`, etc...). This can also be used to reference a font hosted elsewhere using http address.

### Adding a custom font defined in CSS

First, the font needs to be defined in CSS.

```css
/* First way: defined locally using data uri */
@font-face {
font-family: "Symbols";
/* winjs-symbols.woff2: https://github.com/Microsoft/fonts/tree/master/Symbols */
font-family: "RobotoAsBase64"; /* XAML: <FontFamily>RobotoAsBase64</FontFamily> */
src: url(data:application/x-font-woff;charset=utf-8;base64,d09GMgABAAA...) format('woff');
}

/* Second way: defined locally using external uri targetting the font file */
@font-face {
font-family: "Roboto"; /* XAML: <FontFamily>CssRoboto</FontFamily> */
src: url(/Roboto.woff) format('woff');
}

/* Third way: Use an external font definition, optionally hosted on a CDN. */
@import url('http://fonts.cdnfonts.com/css/antikythera'); /* XAML: <FontFamily>Antikythera</FontFamily> + others available */
```

Second, you can use it in XAML in this way:

``` xml
<!-- XAML usage of CSS defined font -->

<TextBlock FontFamily="MyCustomFontAsBase64">This text should be rendered using the font defined as base64 in CSS.</TextBlock>

<TextBlock FontFamily="CssRoboto">This text should be rendered using the roboto.woff font referenced in CSS.</TextBlock>

<TextBlock FontFamily="Antikythera">This text should be rendered using the Antikythera font hosted on a CDN.</TextBlock>
```

This type of declaration is required to avoid measuring errors if the font requested by a `TextBlock` or a `FontIcon` needs to be downloaded first. Specifying it using a data-URI ensures the font is readily available.
> [!NOTE]
> This approach is nice and pretty flexible, but not friendly for multi-targetting. Until Uno.UI v4.4, it was the only way to defined custom fonts on this platform.

### Added a custom font from a file defined as application assets

The font names are referenced based on the `#` name, so:
When the application is multi-targetted, this approach is simpler because no CSS manipulation is required.

1. Add font file as `Content` build action in the application's head project.

2. Reference it using the format is the same as Windows:

```xml
<Setter Property="FontFamily" Value="/Assets/Fonts/yourfont01.ttf#Roboto" />
```
```xml
<Setter Property="FontFamily"
Value="ms-appx:///Assets/Fonts/yourfont.ttf#Your Font Name" />
```

Will match the following `@font-face`:
or

```css
@font-face {
font-family: "Roboto";
...
}
```
```xml
<Setter Property="FontFamily" Value="ms-appx:///Assets/Fonts/yourfont01.ttf#Roboto" />
```

In case your `FontFamily` value does not contain `#`, Uno falls back to the font file name. Hence for:
or

```xml
<!-- This is exclusive to Wasm platform -->
<Setter Property="FontFamily" Value="https://fonts.cdnfonts.com/s/71084/antikythera.woff#Antikythera" />
```
```xml
<Setter Property="FontFamily"
Value="ms-appx:///Assets/Fonts/yourfont.ttf" />
Expand All @@ -92,6 +135,33 @@ Will match:
...
}
```

> [!NOTE]
> The `#` part is optional and is there for cross-platform compatibilty. It is completely ignored on Uno WASM and can be omitted.

> [!TIP]
> Even if the font is defined in CSS, it could still be useful to preload it, since the browser won't parse the font file until is it actually used by the content. Preloading it will force the browser to do this sooner, resulting in a better user experience. This will also prevent the application from doing a new _measure_ phase once the font is loaded.

### Fonts preloading on WebAssembly

On Wasm platform, fonts files are loaded by the browser and can take time to load, resulting in a performance degradation and potential flicking when the font is actually available for rendering. In order to prevent this, it is possible to instruct the browser to preload the font before the rendering:

``` csharp
// Preloading of font families on Wasm. Add this before the Application.Start() in the Program.cs

public static void main(string[] orgs)
{
// Add this in your application to preload a font.
// You can add more than one, but preload too many fonts could hurt user experience.
// IMPORTANT: The string parameter should be exactly the same string (including casing)
// used as FontFamily in the application.
Windows.UI.Xaml.Media.FontFamilyHelper.Preload("ms-appx:///Assets/Fonts/yourfont01.ttf#ApplicationFont01");
Windows.UI.Xaml.Media.FontFamilyHelper.Preload("https://fonts.cdnfonts.com/s/71084/antikythera.woff#Antikythera");
Windows.UI.Xaml.Media.FontFamilyHelper.Preload("Roboto");
jeromelaban marked this conversation as resolved.
Show resolved Hide resolved

Windows.UI.Xaml.Application.Start(_ => _app = new App());
```

## Custom Fonts on macOS

Fonts must be placed in the `Resources/Fonts` folder of the head project, be marked as
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
16 changes: 16 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -4041,6 +4041,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media\FontTests\DynamicFont.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media\Geometry\ClosedFigurePage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -7242,6 +7246,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media\BrushesTests\SolidColorBrush_Color_Changed.xaml.cs">
<DependentUpon>SolidColorBrush_Color_Changed.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media\FontTests\DynamicFont.xaml.cs">
<DependentUpon>DynamicFont.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media\Geometry\ClosedFigurePage.xaml.cs">
<DependentUpon>ClosedFigurePage.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -8481,6 +8488,11 @@
<Content Include="$(MSBuildThisFileDirectory)Assets\Diagnostics\movie_type_dbox.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\DropdownButton.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\FlipView.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\Even Badder Mofo.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\FamilyGuy-4grW.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\Nillambari-K7y1W.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\Renaiss-Italic.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\RoteFlora.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Fonts\uno-fluentui-assets.ttf" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Formats\animated.gif" />
<Content Include="$(MSBuildThisFileDirectory)Assets\Formats\uno-overalls.bmp" />
Expand Down Expand Up @@ -8508,6 +8520,10 @@
<Content Include="$(MSBuildThisFileDirectory)Assets\MenuBar.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\MenuFlyout.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\RedSquare.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\RemoteFonts\antikythera.woff" />
<Content Include="$(MSBuildThisFileDirectory)Assets\RemoteFonts\antikytheraoutline.woff" />
<Content Include="$(MSBuildThisFileDirectory)Assets\RemoteFonts\antikytheraoutlineital.woff" />
<Content Include="$(MSBuildThisFileDirectory)Assets\RemoteFonts\GALACTIC VANGUARDIAN NCV.woff" />
<Content Include="$(MSBuildThisFileDirectory)Assets\square100.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\test_image_200_200.png" />
<Content Include="$(MSBuildThisFileDirectory)Assets\theme-dark\ThemeTestImage.png" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<Page x:Class="UITests.Windows_UI_Xaml_Media.FontTests.DynamicFont"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Windows_UI_Xaml_Media.FontTests"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<StackPanel Spacing="20">
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="ms-appx:///Assets/Fonts/Even Badder Mofo.ttf#EvenBadderMofo">(ms-appx) This is a test</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="ms-appx:///Assets/Fonts/FamilyGuy-4grW.ttf">(ms-appx) This is a test</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="ms-appx:///Assets/Fonts/Nillambari-K7y1W.ttf">(ms-appx) This is a test</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikythera.woff">(https) This is a test rendering a string using external uri. [WASM ONLY]</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikytheraoutlineital.woff">(https) This is a test rendering a string using external uri. [WASM ONLY]</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikytheraoutline.woff">(https) This is a test rendering a string using external uri. [WASM ONLY]</TextBlock>
</Border>
<Border BorderBrush="Blue"
BorderThickness="3"
HorizontalAlignment="Left">
<TextBlock FontSize="20"
FontFamily="https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/GALACTIC%20VANGUARDIAN%20NCV.woff">(https) This is a test rendering a string using external uri. [WASM ONLY]</TextBlock>
</Border>

</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Windows.UI.Xaml.Controls;
using Uno.UI.Samples.Controls;

namespace UITests.Windows_UI_Xaml_Media.FontTests
{
[Sample]
public partial class DynamicFont : Page
{
public DynamicFont()
{
this.InitializeComponent();
}
}
}
8 changes: 6 additions & 2 deletions src/Uno.Foundation/Uno.Foundation.Wasm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
</PropertyGroup>

<Import Project="../netcore-build.props"/>
<Import Project="../targetframework-override.props"/>
<Import Project="../netcore-build.props" />
<Import Project="../targetframework-override.props" />

<PropertyGroup>
<AssemblyName>Uno.Foundation</AssemblyName>
Expand All @@ -19,6 +19,10 @@
<PlatformItemsBasePath>.\</PlatformItemsBasePath>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\SourceGenerators\Uno.UI.SourceGenerators\Helpers\Nullable.cs" Link="Extensions\Nullable.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Uno.SourceGenerationTasks" />
<PackageReference Include="System.Memory" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public void With_Pure_Name()
{
var fontName = "My happy font";
var fontFamily = new FontFamily(fontName);
Assert.AreEqual(fontName, fontFamily.ParsedSource);
Assert.AreEqual(fontName, fontFamily.CssFontName);
}

[TestMethod]
Expand All @@ -21,7 +21,7 @@ public void With_Path_And_Hash()
var fontName = "My font name";
var fontFamilyPath = $"/Assets/Data/Fonts/MyFont.ttf#{fontName}";
var fontFamily = new FontFamily(fontFamilyPath);
Assert.AreEqual(fontName, fontFamily.ParsedSource);
Assert.AreEqual(fontName, fontFamily.CssFontName);
}

[TestMethod]
Expand All @@ -30,7 +30,7 @@ public void With_Path_Without_Hash()
var fontName = "FontName";
var fontFamilyPath = $"/Assets/Data/Fonts/{fontName}.ttf";
var fontFamily = new FontFamily(fontFamilyPath);
Assert.AreEqual(fontName, fontFamily.ParsedSource);
Assert.AreEqual(fontName, fontFamily.CssFontName);
}

[TestMethod]
Expand All @@ -39,7 +39,7 @@ public void With_Msappx()
var fontName = "Msappx font name";
var fontFamilyPath = $"ms-appx:///Assets/Data/Fonts/MyFont.ttf#{fontName}";
var fontFamily = new FontFamily(fontFamilyPath);
Assert.AreEqual(fontName, fontFamily.ParsedSource);
Assert.AreEqual(fontName, fontFamily.CssFontName);
}

[TestMethod]
Expand All @@ -48,7 +48,7 @@ public void Without_Path_With_Hash()
var fontName = "Font name";
var fontFamilyPath = $"SomeFont.woff2#{fontName}";
var fontFamily = new FontFamily(fontFamilyPath);
Assert.AreEqual(fontName, fontFamily.ParsedSource);
Assert.AreEqual(fontName, fontFamily.CssFontName);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#if __WASM__
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using static Private.Infrastructure.TestServices;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media
{
[TestClass]
[RunsOnUIThread]
public class Given_FontFamilyLoader
{
[TestMethod]
[DataRow("ms-appx:///Assets/Fonts/Even Badder Mofo.ttf#EvenBadderMofo")]
[DataRow("ms-appx:///Assets/Fonts/FamilyGuy-4grW.ttf")]
[DataRow("ms-appx:///Assets/Fonts/Nillambari-K7y1W.ttf")]
[DataRow("https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikythera.woff")]
[DataRow("https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikytheraoutlineital.woff")]
[DataRow("https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/antikytheraoutline.woff")]
[DataRow("https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/GALACTIC%20VANGUARDIAN%20NCV.woff")]
public async Task With_FontPath(string fontPath)
{
var SUT = new TextBlock() {
Text="Hellow Uno!",
FontFamily = new(fontPath)
};

var loader = FontFamilyLoader.GetLoaderForFontFamily(SUT.FontFamily);
Assert.IsNotNull(loader, "loader");
Assert.IsFalse(loader.IsLoading, "IsLoading");

WindowHelper.WindowContent = SUT;
await WindowHelper.WaitForLoaded(SUT);

var sw = Stopwatch.StartNew();
while ((!loader.IsLoaded || loader.IsLoading) && sw.Elapsed < TimeSpan.FromSeconds(10))
{
await Task.Delay(100);
await WindowHelper.WaitForIdle();
}

Assert.IsFalse(loader.IsLoading, "IsLoading");
Assert.IsTrue(loader.IsLoaded, "IsLoaded");
}

[TestMethod]
public async Task When_FailedLoading()
{
var family = new FontFamily("https://raw.githubusercontent.com/unoplatform/uno/8751b7af65f5426d3a1f91274b8663465452411c/src/SamplesApp/UITests.Shared/Assets/RemoteFonts/INVALIDFONT.woff");
var loader = FontFamilyLoader.GetLoaderForFontFamily(family);
Assert.IsFalse(await loader.LoadFontAsync());
}
}
}
#endif
5 changes: 5 additions & 0 deletions src/Uno.UI/LinkerDefinition.Wasm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<type fullname="Uno.UI.Xaml.Controls.SystemFocusVisual">
<method name="DispatchNativePositionChange" />
</type>

<type fullname="Windows.UI.Xaml.Media.FontFamilyLoader">
<method name="NotifyFontLoaded" />
<method name="NotifyFontLoadFailed" />
</type>
</assembly>

<assembly fullname="Uno">
Expand Down
Loading