Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
7 changes: 1 addition & 6 deletions lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,7 @@ class FallbackFontDownloadQueue {
final Uint8List bytes = downloadedData[url]!;
FontFallbackData.instance.registerFallbackFont(font.name, bytes);
if (pendingFonts.isEmpty) {
_fontsLoading = renderer.fontCollection.ensureFontsLoaded();
try {
await _fontsLoading;
} finally {
_fontsLoading = null;
}
renderer.fontCollection.registerDownloadedFonts();
sendFontChangeMessage();
}
}
Expand Down
148 changes: 84 additions & 64 deletions lib/web_ui/lib/src/engine/canvaskit/fonts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

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

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

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

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

/// Returns fonts that have been downloaded and parsed.
/// Returns fonts that have been downloaded, registered, and parsed.
///
/// This should only be used in tests.
List<RegisteredFont>? get debugDownloadedFonts {
List<RegisteredFont>? get debugRegisteredFonts {
if (!assertionsEnabled) {
return null;
}
return _downloadedFonts;
return _registeredFonts;
}

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

@override
Future<void> ensureFontsLoaded() async {
await _loadFonts();

void _registerWithFontProvider() {
if (fontProvider != null) {
fontProvider!.delete();
fontProvider = null;
}
fontProvider = canvasKit.TypefaceFontProvider.Make();
familyToFontMap.clear();

for (final RegisteredFont font in _downloadedFonts) {
for (final RegisteredFont font in _registeredFonts) {
fontProvider!.registerFont(font.bytes, font.family);
familyToFontMap
.putIfAbsent(font.family, () => <SkFont>[])
Expand All @@ -75,21 +69,6 @@ class SkiaFontCollection implements FontCollection {
}
}

/// Loads all of the unloaded fonts in [_pendingFonts] and adds them
/// to [_downloadedFonts].
Future<void> _loadFonts() async {
if (_pendingFonts.isEmpty) {
return;
}
final List<RegisteredFont?> loadedFonts = await Future.wait(_pendingFonts);
for (final RegisteredFont? font in loadedFonts) {
if (font != null) {
_downloadedFonts.add(font);
}
}
_pendingFonts.clear();
}

@override
Future<void> loadFontFromList(Uint8List list, {String? fontFamily}) async {
if (fontFamily == null) {
Expand All @@ -103,8 +82,8 @@ class SkiaFontCollection implements FontCollection {
final SkTypeface? typeface =
canvasKit.Typeface.MakeFreeTypeFaceFromData(list.buffer);
if (typeface != null) {
_downloadedFonts.add(RegisteredFont(list, fontFamily, typeface));
await ensureFontsLoaded();
_registeredFonts.add(RegisteredFont(list, fontFamily, typeface));
_registerWithFontProvider();
} else {
printWarning('Failed to parse font family "$fontFamily"');
return;
Expand All @@ -113,7 +92,7 @@ class SkiaFontCollection implements FontCollection {

/// Loads fonts from `FontManifest.json`.
@override
Future<void> registerFonts(AssetManager assetManager) async {
Future<void> downloadAssetFonts(AssetManager assetManager) async {
ByteData byteData;

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

final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];

for (final Map<String, dynamic> fontFamily
in fontManifest.cast<Map<String, dynamic>>()) {
final String family = fontFamily.readString('family');
final List<dynamic> fontAssets = fontFamily.readList('fonts');
for (final dynamic fontAssetItem in fontAssets) {
final Map<String, dynamic> fontAsset = fontAssetItem as Map<String, dynamic>;
final String asset = fontAsset.readString('asset');
_registerFont(assetManager.getAssetUrl(asset), family);
_downloadFont(pendingFonts, assetManager.getAssetUrl(asset), family);
}
}

/// We need a default fallback font for CanvasKit, in order to
/// avoid crashing while laying out text with an unregistered font. We chose
/// Roboto to match Android.
if (!_isFontFamilyRegistered('Roboto')) {
if (!_isFontFamilyDownloaded('Roboto')) {
// Download Roboto and add it to the font buffers.
_registerFont(_robotoUrl, 'Roboto');
_downloadFont(pendingFonts, _robotoUrl, 'Roboto');
}

final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
_unregisteredFonts.addAll(completedPendingFonts.whereType<UnregisteredFont>());
}

@override
void registerDownloadedFonts() {
RegisteredFont? makeRegisterFont(ByteBuffer buffer, String url, String family) {
final Uint8List bytes = buffer.asUint8List();
final SkTypeface? typeface =
canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer);
if (typeface != null) {
return RegisteredFont(bytes, family, typeface);
} else {
printWarning('Failed to load font $family at $url');
printWarning('Verify that $url contains a valid font.');
return null;
}
}

for (final UnregisteredFont unregisteredFont in _unregisteredFonts) {
final RegisteredFont? registeredFont = makeRegisterFont(
unregisteredFont.bytes,
unregisteredFont.url,
unregisteredFont.family
);
if (registeredFont != null) {
_registeredFonts.add(registeredFont);
}
}

_unregisteredFonts.clear();
_registerWithFontProvider();
}

/// Whether the [fontFamily] was registered and/or loaded.
bool _isFontFamilyRegistered(String fontFamily) {
return _registeredFontFamilies.contains(fontFamily);
bool _isFontFamilyDownloaded(String fontFamily) {
return _downloadedFontFamilies.contains(fontFamily);
}

/// Loads the Ahem font, unless it's already been loaded using
/// `FontManifest.json` (see [registerFonts]).
/// `FontManifest.json` (see [downloadAssetFonts]).
///
/// `FontManifest.json` has higher priority than the default test font URLs.
/// This allows customizing test environments where fonts are loaded from
/// different URLs.
@override
void debugRegisterTestFonts() {
if (!_isFontFamilyRegistered(ahemFontFamily)) {
_registerFont(ahemFontUrl, ahemFontFamily);
Future<void> debugDownloadTestFonts() async {
final List<Future<UnregisteredFont?>> pendingFonts = <Future<UnregisteredFont?>>[];
if (!_isFontFamilyDownloaded(ahemFontFamily)) {
_downloadFont(pendingFonts, ahemFontUrl, ahemFontFamily);
}
if (!_isFontFamilyRegistered(robotoFontFamily)) {
_registerFont(robotoTestFontUrl, robotoFontFamily);
if (!_isFontFamilyDownloaded(robotoFontFamily)) {
_downloadFont(pendingFonts, robotoTestFontUrl, robotoFontFamily);
}
if (!_isFontFamilyRegistered(robotoVariableFontFamily)) {
_registerFont(robotoVariableTestFontUrl, robotoVariableFontFamily);
if (!_isFontFamilyDownloaded(robotoVariableFontFamily)) {
_downloadFont(pendingFonts, robotoVariableTestFontUrl, robotoVariableFontFamily);
}

final List<UnregisteredFont?> completedPendingFonts = await Future.wait(pendingFonts);
_unregisteredFonts.addAll(completedPendingFonts.whereType<UnregisteredFont>());

// Ahem must be added to font fallbacks list regardless of where it was
// downloaded from.
FontFallbackData.instance.globalFontFallbacks.add(ahemFontFamily);
}

void _registerFont(String url, String family) {
Future<RegisteredFont?> downloadFont() async {
void _downloadFont(
List<Future<UnregisteredFont?>> waitUnregisteredFonts,
String url,
String family
) {
Future<UnregisteredFont?> downloadFont() async {
ByteBuffer buffer;
try {
buffer = await httpFetch(url).then(_getArrayBuffer);
return UnregisteredFont(buffer, url, family);
} catch (e) {
printWarning('Failed to load font $family at $url');
printWarning(e.toString());
return null;
}

final Uint8List bytes = buffer.asUint8List();
final SkTypeface? typeface =
canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer);
if (typeface != null) {
return RegisteredFont(bytes, family, typeface);
} else {
printWarning('Failed to load font $family at $url');
printWarning('Verify that $url contains a valid font.');
return null;
}
}

_registeredFontFamilies.add(family);
_pendingFonts.add(downloadFont());
_downloadedFontFamilies.add(family);
waitUnregisteredFonts.add(downloadFont());
}


Expand Down Expand Up @@ -249,3 +261,11 @@ class RegisteredFont {
/// This is used to determine which code points are supported by this font.
final SkTypeface typeface;
}

/// Represents a font that has been downloaded but not registered.
class UnregisteredFont {
const UnregisteredFont(this.bytes, this.url, this.family);
final ByteBuffer bytes;
final String url;
final String family;
}
25 changes: 22 additions & 3 deletions lib/web_ui/lib/src/engine/fonts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,33 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:typed_data';

import 'assets.dart';

abstract class FontCollection {

/// Fonts loaded with [loadFontFromList] do not need to be registered
/// with [registerDownloadedFonts]. Fonts are both downloaded and registered
/// with [loadFontFromList] calls.
Future<void> loadFontFromList(Uint8List list, {String? fontFamily});
Future<void> ensureFontsLoaded();
Future<void> registerFonts(AssetManager assetManager);
void debugRegisterTestFonts();

/// Completes when fonts from FontManifest.json have been downloaded.
Future<void> downloadAssetFonts(AssetManager assetManager);

/// Registers both downloaded fonts and fallback fonts with the TypefaceFontProvider.
///
/// Downloading of fonts happens separately from registering of fonts so that
/// the download step can happen concurrently with the initalization of the renderer.
///
/// The correct order of calls to register downloaded fonts:
/// 1) [downloadAssetFonts]
/// 2) [registerDownloadedFonts]
///
/// For fallbackFonts, call registerFallbackFont (see font_fallbacks.dart)
/// for each fallback font before calling [registerDownloadedFonts]
void registerDownloadedFonts();
FutureOr<void> debugDownloadTestFonts();
void clear();
}
17 changes: 10 additions & 7 deletions lib/web_ui/lib/src/engine/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,12 @@ Future<void> initializeEngineServices({
}
};

await renderer.initialize();

assetManager ??= const AssetManager();
await _setAssetManager(assetManager);
await renderer.fontCollection.ensureFontsLoaded();
_setAssetManager(assetManager);

Future<void> initializeRendererCallback () async => renderer.initialize();
await Future.wait<void>(<Future<void>>[initializeRendererCallback(), _downloadAssetFonts()]);
renderer.fontCollection.registerDownloadedFonts();
_initializationState = DebugEngineInitializationState.initializedServices;
}

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

Future<void> _setAssetManager(AssetManager assetManager) async {
void _setAssetManager(AssetManager assetManager) {
assert(assetManager != null, 'Cannot set assetManager to null');
if (assetManager == _assetManager) {
return;
}

_assetManager = assetManager;
}

Future<void> _downloadAssetFonts() async {
renderer.fontCollection.clear();

if (_assetManager != null) {
await renderer.fontCollection.registerFonts(assetManager);
await renderer.fontCollection.downloadAssetFonts(_assetManager!);
}

if (ui.debugEmulateFlutterTesterEnvironment) {
renderer.fontCollection.debugRegisterTestFonts();
await renderer.fontCollection.debugDownloadTestFonts();
}
}

Expand Down
Loading