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

Allow custom implementations for font data loading. #611

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions packages/google_fonts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,27 @@ For HTTP fetching to work, certain platforms require additional steps when runni

Learn more at https://docs.flutter.dev/development/data-and-backend/networking#platform-notes.

### Custom font loaders

Once the data for a font is successfully retrieved, it must be loaded into the Flutter engine for use. The default font loader, `DefaultGoogleFontsLoader`, uses the flutter `FontLoader`. You may also provide a custom font loader by implementing the `GoogleFontsLoader` interface and passing it in the global config object. For example:

```dart
// Custom font loader that performs some logging and then defers to the default implementation.
class MyFontLoader implements GoogleFontsLoader {
final _impl = DefaultGoogleFontsLoader();

@override
Future<void> loadFont(String familyName, Future<ByteData> bytes) async {
final data = await bytes;
log('Loaded font $familyName; size ${data.lengthInBytes}');
return _impl.loadFont(familyName, Future.value(data));
}
}

// Elsewhere:
GoogleFonts.config.fontLoader = MyFontLoader();
```

## Bundling fonts when releasing

The `google_fonts` package will automatically use matching font files in your `pubspec.yaml`'s
Expand Down
6 changes: 6 additions & 0 deletions packages/google_fonts/lib/google_fonts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart';

import 'src/google_fonts_base.dart';
import 'src/google_fonts_loader.dart';
import 'src/google_fonts_parts/part_a.dart';
import 'src/google_fonts_parts/part_b.dart';
import 'src/google_fonts_parts/part_c.dart';
Expand Down Expand Up @@ -36,12 +37,17 @@ import 'src/google_fonts_parts/part_x.dart';
import 'src/google_fonts_parts/part_y.dart';
import 'src/google_fonts_parts/part_z.dart';

export 'src/google_fonts_loader.dart';

/// A collection of properties used to specify custom behavior of the
/// GoogleFonts library.
class _Config {
/// Whether or not the GoogleFonts library can make requests to
/// [fonts.google.com](https://fonts.google.com/) to retrieve font files.
var allowRuntimeFetching = true;

/// The instance used to load font data into the Flutter engine.
GoogleFontsLoader fontLoader = DefaultGoogleFontsLoader();
}

/// Provides configuration, and static methods to obtain [TextStyle]s and [TextTheme]s.
Expand Down
5 changes: 2 additions & 3 deletions packages/google_fonts/lib/src/google_fonts_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,8 @@ Future<void> loadFontByteData(
final fontData = await byteData;
if (fontData == null) return;

final fontLoader = FontLoader(familyWithVariantString);
fontLoader.addFont(Future.value(fontData));
await fontLoader.load();
final fontLoader = GoogleFonts.config.fontLoader;
await fontLoader.loadFont(familyWithVariantString, Future.value(fontData));
}

/// Returns [GoogleFontsVariant] from [variantsToCompare] that most closely
Expand Down
21 changes: 21 additions & 0 deletions packages/google_fonts/lib/src/google_fonts_loader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'dart:typed_data';

import 'package:flutter/services.dart' show FontLoader;

/// Abstract base class for implementations that load font data into the Flutter
/// engine.
abstract class GoogleFontsLoader {
/// Loads the given font family and its font data into the Flutter engine,
/// making the font available for use.
Future<void> loadFont(String familyName, Future<ByteData> bytes);
}

/// The default font loader, using the Flutter default [FontLoader].
class DefaultGoogleFontsLoader implements GoogleFontsLoader {
@override
Future<void> loadFont(String familyName, Future<ByteData> bytes) async {
final loader = FontLoader(familyName);
loader.addFont(bytes);
await loader.load();
}
}
17 changes: 17 additions & 0 deletions packages/google_fonts/test/load_font_if_necessary_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ class FakePathProviderPlatform extends Fake
}
}

class MockFontLoader extends Mock implements GoogleFontsLoader {
@override
Future<void> loadFont(String? familyName, Future<ByteData>? bytes) {
super.noSuchMethod(Invocation.method(#loadFont, [familyName, bytes]));
return Future.value();
}
}

const _fakeResponse = 'fake response body - success';
// The number of bytes in _fakeResponse.
const _fakeResponseLengthInBytes = 28;
Expand Down Expand Up @@ -95,12 +103,15 @@ void overridePrint(Future<void> Function() testFn) => () {
void main() {
late Directory directory;
late MockHttpClient mockHttpClient;
late MockFontLoader mockFontLoader;

setUp(() async {
mockHttpClient = MockHttpClient();
httpClient = mockHttpClient;
assetManifest = MockAssetManifest();
mockFontLoader = MockFontLoader();
GoogleFonts.config.allowRuntimeFetching = true;
GoogleFonts.config.fontLoader = DefaultGoogleFontsLoader();
when(mockHttpClient.gets(any)).thenAnswer((_) async {
return http.Response(_fakeResponse, 200);
});
Expand Down Expand Up @@ -355,6 +366,12 @@ void main() {
});
});

test("loadFontIfNecessary uses specified font loader", () async {
GoogleFonts.config.fontLoader = mockFontLoader;
await loadFontIfNecessary(fakeDescriptor);
verify(mockFontLoader.loadFont(any, any)).called(1);
});

test("loadFontByteData doesn't fail", () {
expect(
() async => loadFontByteData('fontFamily', Future.value(ByteData(0))),
Expand Down