Skip to content

Commit 9416e72

Browse files
[Docs] - Add page about loading fonts and Material icons
1 parent e707e72 commit 9416e72

File tree

3 files changed

+182
-5
lines changed

3 files changed

+182
-5
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: Load Fonts and Icons
3+
navOrder: 32
4+
---
5+
By default, in golden tests, Flutter replaces all fonts with a big blocky font called
6+
Ahem. Flutter also chooses not to load the standard Material and Cupertino icon sets.
7+
8+
## Load Fonts
9+
The easiest way to load your real app fonts in golden tests is to use the `loadAppFonts()`
10+
function in `flutter_test_goldens`. This method is a port from the discontinued `golden_toolkit`
11+
package.
12+
13+
```dart
14+
await loadAppFonts();
15+
```
16+
17+
The `loadAppFonts()` method can be called from anywhere. However, it's important to know
18+
that once you load app fonts, they remain loaded for the remainder of the test execution.
19+
Therefore, app fonts are typically loaded for all tests. This can be done from a special
20+
file that Flutter automatically looks for, called `flutter_test_config.dart`. Place that
21+
file at the root of your test directory.
22+
23+
```dart
24+
import 'package:flutter_test_goldens/flutter_test_goldens.dart';
25+
26+
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
27+
await loadAppfonts();
28+
29+
return testMain();
30+
}
31+
```
32+
33+
### How Font Loading Works
34+
The `loadAppFonts()` isn't magic. It may be helpful to understand what it's doing
35+
internally.
36+
37+
At a high level loading app fonts requires the following steps:
38+
* Load the font manifest, which lists all the app's fonts
39+
* Iterate through each font
40+
* Determine the appropriate name to load the font
41+
* Load the font into Flutter's `FontLoader`
42+
43+
The following code replicates the implementation of `loadAppFonts`, which was
44+
originally shipped with `golden_toolkit`. Educational comments have been added.
45+
46+
```dart
47+
Future<void> loadAppFonts() async {
48+
TestWidgetsFlutterBinding.ensureInitialized();
49+
50+
// Flutter stores the font manifest in a JSON file within the root app
51+
// asset bundle. Load it and parse it.
52+
final fontManifest = await rootBundle.loadStructuredData<Iterable<dynamic>>(
53+
'FontManifest.json',
54+
(string) async => json.decode(string),
55+
);
56+
57+
// Iterate through each font data entry in the JSON and try to load the font.
58+
for (final Map<String, dynamic> font in fontManifest) {
59+
// Create a `FontLoader` for this font family (which may include multiple fonts,
60+
// e.g., normal, bold, italic).
61+
//
62+
// Special steps need to be taken to load default fonts like Roboto and SF Pro.
63+
// Those steps are taken by `derivedFontFamily()`.
64+
final fontLoader = FontLoader(derivedFontFamily(font));
65+
66+
for (final Map<String, dynamic> fontType in font['fonts']) {
67+
// Add each font file for this font family, e.g., normal, bold, italic.
68+
fontLoader.addFont(rootBundle.load(fontType['asset']));
69+
}
70+
71+
// Load the font.
72+
await fontLoader.load();
73+
}
74+
}
75+
76+
/// There is no way to easily load the Roboto or Cupertino fonts.
77+
/// To make them available in tests, a package needs to include their own copies of them.
78+
///
79+
/// GoldenToolkit supplies Roboto because it is free to use.
80+
///
81+
/// However, when a downstream package includes a font, the font family will be prefixed with
82+
/// `/packages/<package name>/<fontFamily>` in order to disambiguate when multiple packages include
83+
/// fonts with the same name.
84+
///
85+
/// Ultimately, the font loader will load whatever we tell it, so if we see a font that looks like
86+
/// a Material or Cupertino font family, let's treat it as the main font family
87+
String derivedFontFamily(Map<String, dynamic> fontDefinition) {
88+
if (!fontDefinition.containsKey('family')) {
89+
return '';
90+
}
91+
92+
final String fontFamily = fontDefinition['family'];
93+
94+
if (_overridableFonts.contains(fontFamily)) {
95+
return fontFamily;
96+
}
97+
98+
if (fontFamily.startsWith('packages/')) {
99+
final fontFamilyName = fontFamily.split('/').last;
100+
if (_overridableFonts.any((font) => font == fontFamilyName)) {
101+
return fontFamilyName;
102+
}
103+
} else {
104+
for (final Map<String, dynamic> fontType in fontDefinition['fonts']) {
105+
final String? asset = fontType['asset'];
106+
if (asset != null && asset.startsWith('packages')) {
107+
final packageName = asset.split('/')[1];
108+
return 'packages/$packageName/$fontFamily';
109+
}
110+
}
111+
}
112+
return fontFamily;
113+
}
114+
115+
const List<String> _overridableFonts = [
116+
'Roboto',
117+
'.SF UI Display',
118+
'.SF UI Text',
119+
'.SF Pro Text',
120+
'.SF Pro Display',
121+
];
122+
```
123+
124+
## Load Material Icons
125+
Material icons aren't available in golden tests because they come from a font, and
126+
Flutter doesn't load any fonts in golden tests.
127+
128+
The easiest way to load Material icons is to use the `loadMaterialIconsFont()` function
129+
in `flutter_test_goldens`. Once loaded, the icons appear in all subsequent tests,
130+
therefore, most developers choose to load icons for all tests. To do this, you
131+
can load icons in a `flutter_test_config.dart` file at the root of your test suite.
132+
133+
```dart
134+
import 'package:flutter_test_goldens/flutter_test_goldens.dart';
135+
136+
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
137+
await loadMaterialIconsFont();
138+
139+
return testMain();
140+
}
141+
```
142+
143+
### How Material Icon Loading Works
144+
It may be helpful to know how Material icons are loaded by `loadMaterialIconsFont()`.
145+
146+
The following code is a copy of `loadMaterialIconsFont()` for reference.
147+
148+
```dart
149+
Future<void> loadMaterialIconsFont() async {
150+
const FileSystem fs = LocalFileSystem();
151+
const Platform platform = LocalPlatform();
152+
final Directory flutterRoot = fs.directory(platform.environment['FLUTTER_ROOT']);
153+
154+
// The following path locates the Material icons within Flutter's cache.
155+
// This is a magical path that's not publicly exposed by Flutter, but this
156+
// is the best that the Flutter team has given us.
157+
final File iconFont = flutterRoot.childFile(
158+
fs.path.join(
159+
'bin',
160+
'cache',
161+
'artifacts',
162+
'material_fonts',
163+
'MaterialIcons-Regular.otf',
164+
),
165+
);
166+
167+
// Load the Material icon font.
168+
final Future<ByteData> bytes = Future<ByteData>.value(iconFont.readAsBytesSync().buffer.asByteData());
169+
170+
// Load the Material icon font into Flutter's `FontLoader`.
171+
await (FontLoader('MaterialIcons')..addFont(bytes)).load();
172+
}
173+
174+
extension on Directory {
175+
File childFile(String basename) => File("$path$separator$basename");
176+
}
177+
```

lib/src/fonts/golden_toolkit_fonts.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ import 'package:flutter/material.dart';
2424
import 'package:flutter/services.dart';
2525
import 'package:flutter_test/flutter_test.dart';
2626

27-
///By default, flutter test only uses a single "test" font called Ahem.
27+
/// By default, flutter test only uses a single "test" font called Ahem.
2828
///
29-
///This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.
29+
/// This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.
3030
///
31-
///To make the goldens more useful, we will automatically load any fonts included in your pubspec.yaml as well as from
32-
///packages you depend on.
31+
/// To make the goldens more useful, we will automatically load any fonts included in your pubspec.yaml as well as from
32+
/// packages you depend on.
3333
Future<void> loadAppFonts() async {
3434
TestWidgetsFlutterBinding.ensureInitialized();
3535
final fontManifest = await rootBundle.loadStructuredData<Iterable<dynamic>>(

lib/src/fonts/icons.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'package:platform/platform.dart';
99
/// Loads the Material icons font into the [FontLoader], which doesn't happen by
1010
/// default in widget tests.
1111
///
12-
/// In widget tests. icons render as empty squares. This is because in widget tests
12+
/// In widget tests, Icons render as empty squares. This is because in widget tests
1313
/// the Material icons font isn't loaded by default. Unfortunately, Flutter doesn't
1414
/// provide a first-class ability to load the font, so this method was copied from
1515
/// Flutter to dig into implementation details and load it.

0 commit comments

Comments
 (0)