Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7577fd4

Browse files
authored
download fonts concurrently with wasm (#36813)
1 parent 926bb80 commit 7577fd4

29 files changed

+266
-181
lines changed

lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,12 +447,7 @@ class FallbackFontDownloadQueue {
447447
final Uint8List bytes = downloadedData[url]!;
448448
FontFallbackData.instance.registerFallbackFont(font.name, bytes);
449449
if (pendingFonts.isEmpty) {
450-
_fontsLoading = renderer.fontCollection.ensureFontsLoaded();
451-
try {
452-
await _fontsLoading;
453-
} finally {
454-
_fontsLoading = null;
455-
}
450+
renderer.fontCollection.registerDownloadedFonts();
456451
sendFontChangeMessage();
457452
}
458453
}

lib/web_ui/lib/src/engine/canvaskit/fonts.dart

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:convert';
67
import 'dart:typed_data';
78

@@ -22,44 +23,37 @@ const String _robotoUrl =
2223

2324
/// Manages the fonts used in the Skia-based backend.
2425
class SkiaFontCollection implements FontCollection {
25-
final Set<String> _registeredFontFamilies = <String>{};
26+
final Set<String> _downloadedFontFamilies = <String>{};
2627

27-
/// Fonts that started the download process.
28+
/// Fonts that started the download process, but are not yet registered.
2829
///
29-
/// Once downloaded successfully, this map is cleared and the resulting
30-
/// [RegisteredFont]s are added to [_downloadedFonts].
31-
final List<Future<RegisteredFont?>> _pendingFonts = <Future<RegisteredFont?>>[];
30+
/// /// Once downloaded successfully, this map is cleared and the resulting
31+
/// [UnregisteredFont]s are added to [_registeredFonts].
32+
final List<UnregisteredFont> _unregisteredFonts = <UnregisteredFont>[];
3233

33-
/// Fonts that have been downloaded and parsed into [SkTypeface].
34-
///
35-
/// These fonts may not yet have been registered with the [fontProvider]. This
36-
/// happens after [ensureFontsLoaded] completes.
37-
final List<RegisteredFont> _downloadedFonts = <RegisteredFont>[];
34+
final List<RegisteredFont> _registeredFonts = <RegisteredFont>[];
3835

39-
/// Returns fonts that have been downloaded and parsed.
36+
/// Returns fonts that have been downloaded, registered, and parsed.
4037
///
4138
/// This should only be used in tests.
42-
List<RegisteredFont>? get debugDownloadedFonts {
39+
List<RegisteredFont>? get debugRegisteredFonts {
4340
if (!assertionsEnabled) {
4441
return null;
4542
}
46-
return _downloadedFonts;
43+
return _registeredFonts;
4744
}
4845

4946
final Map<String, List<SkFont>> familyToFontMap = <String, List<SkFont>>{};
5047

51-
@override
52-
Future<void> ensureFontsLoaded() async {
53-
await _loadFonts();
54-
48+
void _registerWithFontProvider() {
5549
if (fontProvider != null) {
5650
fontProvider!.delete();
5751
fontProvider = null;
5852
}
5953
fontProvider = canvasKit.TypefaceFontProvider.Make();
6054
familyToFontMap.clear();
6155

62-
for (final RegisteredFont font in _downloadedFonts) {
56+
for (final RegisteredFont font in _registeredFonts) {
6357
fontProvider!.registerFont(font.bytes, font.family);
6458
familyToFontMap
6559
.putIfAbsent(font.family, () => <SkFont>[])
@@ -75,21 +69,6 @@ class SkiaFontCollection implements FontCollection {
7569
}
7670
}
7771

78-
/// Loads all of the unloaded fonts in [_pendingFonts] and adds them
79-
/// to [_downloadedFonts].
80-
Future<void> _loadFonts() async {
81-
if (_pendingFonts.isEmpty) {
82-
return;
83-
}
84-
final List<RegisteredFont?> loadedFonts = await Future.wait(_pendingFonts);
85-
for (final RegisteredFont? font in loadedFonts) {
86-
if (font != null) {
87-
_downloadedFonts.add(font);
88-
}
89-
}
90-
_pendingFonts.clear();
91-
}
92-
9372
@override
9473
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) async {
9574
if (fontFamily == null) {
@@ -103,8 +82,8 @@ class SkiaFontCollection implements FontCollection {
10382
final SkTypeface? typeface =
10483
canvasKit.Typeface.MakeFreeTypeFaceFromData(list.buffer);
10584
if (typeface != null) {
106-
_downloadedFonts.add(RegisteredFont(list, fontFamily, typeface));
107-
await ensureFontsLoaded();
85+
_registeredFonts.add(RegisteredFont(list, fontFamily, typeface));
86+
_registerWithFontProvider();
10887
} else {
10988
printWarning('Failed to parse font family "$fontFamily"');
11089
return;
@@ -113,7 +92,7 @@ class SkiaFontCollection implements FontCollection {
11392

11493
/// Loads fonts from `FontManifest.json`.
11594
@override
116-
Future<void> registerFonts(AssetManager assetManager) async {
95+
Future<void> downloadAssetFonts(AssetManager assetManager) async {
11796
ByteData byteData;
11897

11998
try {
@@ -134,79 +113,112 @@ class SkiaFontCollection implements FontCollection {
134113
'There was a problem trying to load FontManifest.json');
135114
}
136115

116+
final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];
117+
137118
for (final Map<String, dynamic> fontFamily
138119
in fontManifest.cast<Map<String, dynamic>>()) {
139120
final String family = fontFamily.readString('family');
140121
final List<dynamic> fontAssets = fontFamily.readList('fonts');
141122
for (final dynamic fontAssetItem in fontAssets) {
142123
final Map<String, dynamic> fontAsset = fontAssetItem as Map<String, dynamic>;
143124
final String asset = fontAsset.readString('asset');
144-
_registerFont(assetManager.getAssetUrl(asset), family);
125+
_downloadFont(pendingFonts, assetManager.getAssetUrl(asset), family);
145126
}
146127
}
147128

148129
/// We need a default fallback font for CanvasKit, in order to
149130
/// avoid crashing while laying out text with an unregistered font. We chose
150131
/// Roboto to match Android.
151-
if (!_isFontFamilyRegistered('Roboto')) {
132+
if (!_isFontFamilyDownloaded('Roboto')) {
152133
// Download Roboto and add it to the font buffers.
153-
_registerFont(_robotoUrl, 'Roboto');
134+
_downloadFont(pendingFonts, _robotoUrl, 'Roboto');
135+
}
136+
137+
final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
138+
_unregisteredFonts.addAll(completedPendingFonts.whereType<UnregisteredFont>());
139+
}
140+
141+
@override
142+
void registerDownloadedFonts() {
143+
RegisteredFont? makeRegisterFont(ByteBuffer buffer, String url, String family) {
144+
final Uint8List bytes = buffer.asUint8List();
145+
final SkTypeface? typeface =
146+
canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer);
147+
if (typeface != null) {
148+
return RegisteredFont(bytes, family, typeface);
149+
} else {
150+
printWarning('Failed to load font $family at $url');
151+
printWarning('Verify that $url contains a valid font.');
152+
return null;
153+
}
154+
}
155+
156+
for (final UnregisteredFont unregisteredFont in _unregisteredFonts) {
157+
final RegisteredFont? registeredFont = makeRegisterFont(
158+
unregisteredFont.bytes,
159+
unregisteredFont.url,
160+
unregisteredFont.family
161+
);
162+
if (registeredFont != null) {
163+
_registeredFonts.add(registeredFont);
164+
}
154165
}
166+
167+
_unregisteredFonts.clear();
168+
_registerWithFontProvider();
155169
}
156170

157171
/// Whether the [fontFamily] was registered and/or loaded.
158-
bool _isFontFamilyRegistered(String fontFamily) {
159-
return _registeredFontFamilies.contains(fontFamily);
172+
bool _isFontFamilyDownloaded(String fontFamily) {
173+
return _downloadedFontFamilies.contains(fontFamily);
160174
}
161175

162176
/// Loads the Ahem font, unless it's already been loaded using
163-
/// `FontManifest.json` (see [registerFonts]).
177+
/// `FontManifest.json` (see [downloadAssetFonts]).
164178
///
165179
/// `FontManifest.json` has higher priority than the default test font URLs.
166180
/// This allows customizing test environments where fonts are loaded from
167181
/// different URLs.
168182
@override
169-
void debugRegisterTestFonts() {
170-
if (!_isFontFamilyRegistered(ahemFontFamily)) {
171-
_registerFont(ahemFontUrl, ahemFontFamily);
183+
Future<void> debugDownloadTestFonts() async {
184+
final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];
185+
if (!_isFontFamilyDownloaded(ahemFontFamily)) {
186+
_downloadFont(pendingFonts, ahemFontUrl, ahemFontFamily);
172187
}
173-
if (!_isFontFamilyRegistered(robotoFontFamily)) {
174-
_registerFont(robotoTestFontUrl, robotoFontFamily);
188+
if (!_isFontFamilyDownloaded(robotoFontFamily)) {
189+
_downloadFont(pendingFonts, robotoTestFontUrl, robotoFontFamily);
175190
}
176-
if (!_isFontFamilyRegistered(robotoVariableFontFamily)) {
177-
_registerFont(robotoVariableTestFontUrl, robotoVariableFontFamily);
191+
if (!_isFontFamilyDownloaded(robotoVariableFontFamily)) {
192+
_downloadFont(pendingFonts, robotoVariableTestFontUrl, robotoVariableFontFamily);
178193
}
179194

195+
final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
196+
_unregisteredFonts.addAll(completedPendingFonts.whereType<UnregisteredFont>());
197+
180198
// Ahem must be added to font fallbacks list regardless of where it was
181199
// downloaded from.
182200
FontFallbackData.instance.globalFontFallbacks.add(ahemFontFamily);
183201
}
184202

185-
void _registerFont(String url, String family) {
186-
Future<RegisteredFont?> downloadFont() async {
203+
void _downloadFont(
204+
List<Future<UnregisteredFont?>> waitUnregisteredFonts,
205+
String url,
206+
String family
207+
) {
208+
Future<UnregisteredFont?> downloadFont() async {
187209
ByteBuffer buffer;
188210
try {
189211
buffer = await httpFetch(url).then(_getArrayBuffer);
212+
return UnregisteredFont(buffer, url, family);
190213
} catch (e) {
191214
printWarning('Failed to load font $family at $url');
192215
printWarning(e.toString());
193216
return null;
194217
}
195-
196-
final Uint8List bytes = buffer.asUint8List();
197-
final SkTypeface? typeface =
198-
canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer);
199-
if (typeface != null) {
200-
return RegisteredFont(bytes, family, typeface);
201-
} else {
202-
printWarning('Failed to load font $family at $url');
203-
printWarning('Verify that $url contains a valid font.');
204-
return null;
205-
}
206218
}
207219

208-
_registeredFontFamilies.add(family);
209-
_pendingFonts.add(downloadFont());
220+
_downloadedFontFamilies.add(family);
221+
waitUnregisteredFonts.add(downloadFont());
210222
}
211223

212224

@@ -249,3 +261,11 @@ class RegisteredFont {
249261
/// This is used to determine which code points are supported by this font.
250262
final SkTypeface typeface;
251263
}
264+
265+
/// Represents a font that has been downloaded but not registered.
266+
class UnregisteredFont {
267+
const UnregisteredFont(this.bytes, this.url, this.family);
268+
final ByteBuffer bytes;
269+
final String url;
270+
final String family;
271+
}

lib/web_ui/lib/src/engine/fonts.dart

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,33 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:async';
56
import 'dart:typed_data';
67

78
import 'assets.dart';
89

910
abstract class FontCollection {
11+
12+
/// Fonts loaded with [loadFontFromList] do not need to be registered
13+
/// with [registerDownloadedFonts]. Fonts are both downloaded and registered
14+
/// with [loadFontFromList] calls.
1015
Future<void> loadFontFromList(Uint8List list, {String? fontFamily});
11-
Future<void> ensureFontsLoaded();
12-
Future<void> registerFonts(AssetManager assetManager);
13-
void debugRegisterTestFonts();
16+
17+
/// Completes when fonts from FontManifest.json have been downloaded.
18+
Future<void> downloadAssetFonts(AssetManager assetManager);
19+
20+
/// Registers both downloaded fonts and fallback fonts with the TypefaceFontProvider.
21+
///
22+
/// Downloading of fonts happens separately from registering of fonts so that
23+
/// the download step can happen concurrently with the initalization of the renderer.
24+
///
25+
/// The correct order of calls to register downloaded fonts:
26+
/// 1) [downloadAssetFonts]
27+
/// 2) [registerDownloadedFonts]
28+
///
29+
/// For fallbackFonts, call registerFallbackFont (see font_fallbacks.dart)
30+
/// for each fallback font before calling [registerDownloadedFonts]
31+
void registerDownloadedFonts();
32+
FutureOr<void> debugDownloadTestFonts();
1433
void clear();
1534
}

lib/web_ui/lib/src/engine/initialization.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,12 @@ Future<void> initializeEngineServices({
204204
}
205205
};
206206

207-
await renderer.initialize();
208-
209207
assetManager ??= const AssetManager();
210-
await _setAssetManager(assetManager);
211-
await renderer.fontCollection.ensureFontsLoaded();
208+
_setAssetManager(assetManager);
209+
210+
Future<void> initializeRendererCallback () async => renderer.initialize();
211+
await Future.wait<void>(<Future<void>>[initializeRendererCallback(), _downloadAssetFonts()]);
212+
renderer.fontCollection.registerDownloadedFonts();
212213
_initializationState = DebugEngineInitializationState.initializedServices;
213214
}
214215

@@ -243,22 +244,24 @@ Future<void> initializeEngineUi() async {
243244
AssetManager get assetManager => _assetManager!;
244245
AssetManager? _assetManager;
245246

246-
Future<void> _setAssetManager(AssetManager assetManager) async {
247+
void _setAssetManager(AssetManager assetManager) {
247248
assert(assetManager != null, 'Cannot set assetManager to null');
248249
if (assetManager == _assetManager) {
249250
return;
250251
}
251252

252253
_assetManager = assetManager;
254+
}
253255

256+
Future<void> _downloadAssetFonts() async {
254257
renderer.fontCollection.clear();
255258

256259
if (_assetManager != null) {
257-
await renderer.fontCollection.registerFonts(assetManager);
260+
await renderer.fontCollection.downloadAssetFonts(_assetManager!);
258261
}
259262

260263
if (ui.debugEmulateFlutterTesterEnvironment) {
261-
renderer.fontCollection.debugRegisterTestFonts();
264+
await renderer.fontCollection.debugDownloadTestFonts();
262265
}
263266
}
264267

0 commit comments

Comments
 (0)