diff --git a/.gitignore b/.gitignore index 3dd85b81..8edb557d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ .history .svn/ .firebase/ -.flutter-plugins-dependencies # IntelliJ related *.iml @@ -27,6 +26,7 @@ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies +example/.flutter-plugins-dependencies .packages .pub-cache/ .pub/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a92c43..986a2ab1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 01c27c9e..a00f73d4 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 { @@ -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!) \ No newline at end of file +(so be sure not to rename them!) diff --git a/build/testfile.dill.track.dill b/build/testfile.dill.track.dill deleted file mode 100644 index e9c85e7a..00000000 Binary files a/build/testfile.dill.track.dill and /dev/null differ diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies deleted file mode 100644 index 17d9de06..00000000 --- a/example/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"path_provider","dependencies":[]}]} \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index 1898813e..b819d7e7 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -80,7 +80,7 @@ packages: path: ".." relative: true source: path - version: "0.3.3" + version: "0.3.7" http: dependency: transitive description: diff --git a/lib/src/google_fonts_base.dart b/lib/src/google_fonts_base.dart index 6be3ed51..271b52c3 100755 --- a/lib/src/google_fonts_base.dart +++ b/lib/src/google_fonts_base.dart @@ -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 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; - 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; - 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) async { final anyFontDataFound = byteData != null && await byteData != null; if (anyFontDataFound) { + _loadedFonts.add(familyWithVariantString); final fontLoader = FontLoader(familyWithVariantString); fontLoader.addFont(byteData); await fontLoader.load(); @@ -191,7 +216,12 @@ Future _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); @@ -257,11 +287,15 @@ Future> _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 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) { @@ -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; } diff --git a/pubspec.yaml b/pubspec.yaml index ff9cf338..12b62f12 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: diff --git a/test/load_font_if_necessary_test.dart b/test/load_font_if_necessary_test.dart index 126da012..0af2664f 100644 --- a/test/load_font_if_necessary_test.dart +++ b/test/load_font_if_necessary_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; @@ -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(); @@ -37,6 +47,7 @@ main() { }); tearDown(() { + printLog = []; clearCache(); }); @@ -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( @@ -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)); });