Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
.history
.svn/
.firebase/
.flutter-plugins-dependencies

# IntelliJ related
*.iml
Expand All @@ -27,6 +26,7 @@
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
example/.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## [0.3.7] - 2020-02-03

* Fix asset font loading bug
* Update asset font README instructions

## [0.3.6] - 2020-01-31

* Add a config to the `GoogleFonts` class with an `allowHttp` option.
Expand Down
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The `google_fonts` package for Flutter allows you to easily use any of the 977 f

With the `google_fonts` package, `.ttf` or `.otf` files do not need to be stored in your assets folder and mapped in
the pubspec. Instead, they can be fetched once via http at runtime, and cached in the app's file system. This is ideal for development, and can be the preferred behavior for production apps that
are looking to reduce the app bundle size. Still, you may at any time choose to include the font file in the assets, and the Google Fonts package will prioritize pre-bundled files over http fetching.
are looking to reduce the app bundle size. Still, you may at any time choose to include the font file in the assets, and the Google Fonts package will prioritize pre-bundled files over http fetching.
Because of this, the Google Fonts package allows developers to choose between pre-bundling the fonts and loading them over http, while using the same API.

For example, say you want to use the [Lato](https://fonts.google.com/specimen/Lato) font from Google Fonts in your Flutter app.
Expand Down Expand Up @@ -93,18 +93,15 @@ MaterialApp(
);
```

### Choosing to include a font without having to http fetch it
### Bundling font files in your application's assets

If you want the Google Fonts package to use font files that you have included in your pubspec's
assets (rather than fetching at runtime via http), first download the font files from
[https://fonts.google.com](https://fonts.google.com). Then, create a folder at the top level of
your app directory named `google_fonts`, and copy the font files that you want to be used into that folder.
The `google_fonts` package will automatically use matching font files in your `pubspec.yaml`'s
`assets` (rather than fetching them at runtime via HTTP). Once you've settled on the fonts
you want to use:

![](https://raw.githubusercontent.com/material-foundation/google-fonts-flutter/master/readme_images/google_fonts_folder.png)

You only need to copy the files in for the font weight and font styles that you are using for any
given family. Italic styles will include `Italic` in the filename, and the font weights map to
filenames as follows:
1. Download the font files from [https://fonts.google.com](https://fonts.google.com).
You only need to download the weights and styles you are using for any given family.
Italic styles will include `Italic` in the filename. Font weights map to file names as follows:

```dart
{
Expand All @@ -120,10 +117,14 @@ filenames as follows:
}
```

Finally, make sure you have listed the `google_fonts` folder in your `pubspec.yaml`.
2. Move those fonts to a top-level app directory (e.g. `google_fonts`).

![](https://raw.githubusercontent.com/material-foundation/google-fonts-flutter/master/readme_images/google_fonts_folder.png)

3. Ensure that you have listed the folder (e.g. `google_fonts/`) in your `pubspec.yaml` under `assets`.

![](https://raw.githubusercontent.com/material-foundation/google-fonts-flutter/master/readme_images/google_fonts_pubspec_assets.png)

Note: Since these files are listed as assets, there is no need to list them in the fonts section
Note: Since these files are listed as assets, there is no need to list them in the `fonts` section
of the `pubspec.yaml`. This can be done because the files are consistently named from the Google Fonts API
(so be sure not to rename them!)
(so be sure not to rename them!)
Binary file removed build/testfile.dill.track.dill
Binary file not shown.
1 change: 0 additions & 1 deletion example/.flutter-plugins-dependencies

This file was deleted.

2 changes: 1 addition & 1 deletion example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.3.3"
version: "0.3.7"
http:
dependency: transitive
description:
Expand Down
95 changes: 64 additions & 31 deletions lib/src/google_fonts_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,44 +109,69 @@ TextStyle googleFontsTextStyle({
/// If a font with the [fontName] has already been loaded into memory, then
/// this method does nothing as there is no need to load it a second time.
///
/// Otherwise, this method will first check to see if the font is available on
/// disk. If it is, then it loads it into the [FontLoader]. If it is not on
/// disk, then it fetches it via the [fontUrl], stores it on disk, and loads it
/// into the [FontLoader].
/// Otherwise, this method will first check to see if the font is available
/// as an asset, then on disk. If it isn't, it is fetched via the [fontUrl]
/// and stored on disk. In all cases, the font is loaded into the [FontLoader].
Future<void> loadFontIfNecessary(GoogleFontsDescriptor descriptor) async {
final familyWithVariantString = descriptor.familyWithVariant.toString();
final fontName = descriptor.familyWithVariant.toApiFilenamePrefix();
// If this font has already been loaded, then there is no need to load it
// again.
if (_loadedFonts.contains(familyWithVariantString)) {
return;
}

// If this font can be loaded by the pre-bundled assets, then there is no
// need to load it at all.
final assetManifestJson = await _loadAssetManifestJson();
try {
Future<ByteData> byteData;

if (_isFamilyWithVariantInManifest(
descriptor.familyWithVariant,
assetManifestJson,
)) {
return;
}
// Check if this font can be loaded by the pre-bundled assets.
final assetManifestJson = await _loadAssetManifestJson();
final assetPath = _findFamilyWithVariantAssetPath(
descriptor.familyWithVariant,
assetManifestJson,
);
if (assetPath != null) {
byteData = rootBundle.load(assetPath);
}
if (await byteData != null) {
return _loadFontByteData(familyWithVariantString, byteData);
}

_loadedFonts.add(familyWithVariantString);
// Check if this font can be loaded from the device file system.
if (!kIsWeb) {
byteData = _loadFontFromDeviceFileSystem(familyWithVariantString);
}
if (await byteData != null) {
return _loadFontByteData(familyWithVariantString, byteData);
}

Future<ByteData> byteData;
if (!kIsWeb) {
byteData = _loadFontFromDeviceFileSystem(familyWithVariantString);
}
final localFontFound = byteData != null && await byteData != null;
if (!localFontFound && GoogleFonts.config.allowHttp) {
byteData = _httpFetchFontAndSaveToDevice(
familyWithVariantString,
descriptor.fontUrl,
);
// Attempt to load this font via http, unless disallowed.
if (GoogleFonts.config.allowHttp) {
byteData = _httpFetchFontAndSaveToDevice(
familyWithVariantString,
descriptor.fontUrl,
);
if (await byteData != null) {
return _loadFontByteData(familyWithVariantString, byteData);
}
} else {
throw (Exception(
"GoogleFonts.config.allowHttp is false but font $fontName was not found "
"in the application assets. Ensure $fontName.otf exists in a folder "
"that is included in your pubspec's assets."));
}
} catch (e) {
print('error: google_fonts was unable to load font $fontName because the '
'following exception occured\n$e');
}
}

/// Loads a font with [FontLoader], given its name and byte-representation.
void _loadFontByteData(
String familyWithVariantString, Future<ByteData> byteData) async {
final anyFontDataFound = byteData != null && await byteData != null;
if (anyFontDataFound) {
_loadedFonts.add(familyWithVariantString);
final fontLoader = FontLoader(familyWithVariantString);
fontLoader.addFont(byteData);
await fontLoader.load();
Expand Down Expand Up @@ -191,7 +216,12 @@ Future<ByteData> _httpFetchFontAndSaveToDevice(
throw Exception('Invalid fontUrl: $fontUrl');
}

final response = await httpClient.get(uri);
var response;
try {
response = await httpClient.get(uri);
} catch (e) {
throw Exception('Failed to load font with url: $fontUrl');
}
if (response.statusCode == 200) {
_saveFontToDeviceFileSystem(fontName, response.bodyBytes);
return ByteData.view(response.bodyBytes.buffer);
Expand Down Expand Up @@ -257,11 +287,15 @@ Future<Map<String, dynamic>> _loadAssetManifestJson() async {
}
}

bool _isFamilyWithVariantInManifest(
/// Looks for a matching [familyWithVariant] font, provided the asset manifest.
/// Returns the path of the font asset if found, otherwise an empty string.
String _findFamilyWithVariantAssetPath(
GoogleFontsFamilyWithVariant familyWithVariant,
Map<String, dynamic> manifestJson,
) {
if (manifestJson == null) return false;
if (manifestJson == null) return null;

final apiFilenamePrefix = familyWithVariant.toApiFilenamePrefix();

for (final assetList in manifestJson.values) {
for (final String asset in assetList) {
Expand All @@ -276,13 +310,12 @@ bool _isFamilyWithVariantInManifest(
if (matchingFontSuffix != null) {
final assetWithRemovedExtension =
asset.substring(0, asset.length - matchingFontSuffix.length);
if (assetWithRemovedExtension
.endsWith(familyWithVariant.toApiFilenamePrefix())) {
return true;
if (assetWithRemovedExtension.endsWith(apiFilenamePrefix)) {
return asset;
}
}
}
}

return false;
return null;
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: google_fonts
description: A package to include fonts from fonts.google.com in your flutter app.
version: 0.3.6
version: 0.3.7
homepage: https://github.com/material-foundation/google-fonts-flutter/

environment:
Expand Down
57 changes: 56 additions & 1 deletion test/load_font_if_necessary_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
Expand All @@ -16,6 +17,15 @@ import 'package:path_provider/path_provider.dart';

class MockHttpClient extends Mock implements http.Client {}

var printLog = [];
overridePrint(testFn()) => () {
var spec = new ZoneSpecification(print: (_, __, ___, String msg) {
// Add to log instead of printing to stdout
printLog.add(msg);
});
return Zone.current.fork(specification: spec).run(testFn);
};

main() {
setUp(() async {
httpClient = MockHttpClient();
Expand All @@ -37,6 +47,7 @@ main() {
});

tearDown(() {
printLog = [];
clearCache();
});

Expand All @@ -57,6 +68,36 @@ main() {
verify(httpClient.get(fakeUrl)).called(1);
});

testWidgets('loadFontIfNecessary method throws if font cannot be loaded',
(tester) async {
// Mock a bad response.
when(httpClient.get(any)).thenAnswer((_) async {
return http.Response('fake response body - failure', 300);
});

final fooUrl = Uri.http('fonts.google.com', '/Foo');
final descriptorInAssets = GoogleFontsDescriptor(
familyWithVariant: GoogleFontsFamilyWithVariant(
family: 'Foo',
googleFontsVariant: GoogleFontsVariant(
fontWeight: FontWeight.w900,
fontStyle: FontStyle.italic,
),
),
fontUrl: fooUrl.toString(),
);

// Call loadFontIfNecessary and verify that it prints an error.
overridePrint(() async {
await loadFontIfNecessary(descriptorInAssets);
expect(printLog.length, 1);
expect(
printLog[0],
startsWith('google_fonts was unable to load font Foo-BlackItalic'),
);
});
});

testWidgets('does not call http if config is false', (tester) async {
final fakeUrl = Uri.http('fonts.google.com', '/Foo');
final fakeDescriptor = GoogleFontsDescriptor(
Expand All @@ -72,7 +113,21 @@ main() {

GoogleFonts.config.allowHttp = false;

await loadFontIfNecessary(fakeDescriptor);
// Call loadFontIfNecessary and verify that it prints an error.
overridePrint(() async {
await loadFontIfNecessary(fakeDescriptor);
expect(printLog.length, 1);
expect(
printLog[0],
startsWith("google_fonts was unable to load font Foo-Regular"),
);
expect(
printLog[0],
endsWith(
"Ensure Foo-Regular.otf exists in a folder that is included in your pubspec's assets.",
),
);
});

verifyNever(httpClient.get(anything));
});
Expand Down