Skip to content

Commit 6abc0e0

Browse files
authored
[google_fonts] Add WOFF and WOFF2 font format support for web (#10703)
# Description Web applications benefit significantly from using WOFF2 font files instead of TTF/OTF. WOFF2 is a compressed format specifically designed for web delivery, resulting in smaller file sizes and faster load times. This is especially important for Flutter web apps where every byte counts for initial page load performance. **Size comparison (Roboto Regular):** - TTF: 146,004 bytes - WOFF2: 63,424 bytes (~56% smaller) Currently, `google_fonts` only supports TTF and OTF formats, even on web platforms where WOFF2 would be more efficient. This PR adds support for WOFF2 and WOFF formats on web, allowing developers to bundle more efficient font files for their web applications. ## What This PR adds support for `.woff2` and `.woff` font file extensions when running on web platforms, while maintaining backward compatibility with `.ttf` and `.otf` files. **Changes:** - Add `kIsWeb` check to support web-optimized font formats (`.woff2`, `.woff`) in addition to `.ttf` and `.otf` - Make `findFamilyWithVariantAssetPath` testable by adding an optional `isWeb` parameter (defaults to `kIsWeb`) - Add `@visibleForTesting` annotation to enable comprehensive testing without exposing internal API - Add 20 tests covering web-specific, non-web-specific, and common behavior **Platform behavior:** - **Web:** Supports `.woff2`, `.woff`, `.ttf`, `.otf` - **Non-web:** Supports `.ttf`, `.otf` (unchanged) ## Implementation Notes The implementation maintains the existing behavior where **the order of assets in the manifest determines which file type is chosen** when multiple formats are available for the same font. While this feels a bit odd (ideally, there would be a preference order), this is how the function already works for TTF/OTF. In practice, this is not an issue when only one file type per font is bundled, which is the common case. ## Getting WOFF2 Files Developers can convert TTF/OTF fonts to WOFF2 using Google's `woff2_compress` tool: https://github.com/google/woff2 Example: ```bash woff2_compress Roboto-Regular.ttf ``` ## Testing All existing tests pass, plus 20 new tests that verify: - ✅ WOFF2 and WOFF formats work on web - ✅ WOFF2 and WOFF formats are ignored on non-web platforms - ✅ TTF and OTF continue to work on all platforms - ✅ Common behavior (null handling, family/variant matching) works consistently ## Breaking Changes None. This is a backward-compatible addition that only expands the supported file formats on web platforms. EOF ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 0af5736 commit 6abc0e0

File tree

4 files changed

+204
-9
lines changed

4 files changed

+204
-9
lines changed

packages/google_fonts/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 7.1.0
2+
3+
- Adds support for WOFF2 and WOFF font formats on web platforms when loading fonts bundled with the app, providing improved performance and smaller bundle sizes.
4+
15
## 7.0.2
26

37
- Adds missing public API documentation

packages/google_fonts/lib/src/google_fonts_base.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'package:crypto/crypto.dart';
6+
import 'package:flutter/foundation.dart' show kIsWeb;
67
import 'package:flutter/material.dart';
78
import 'package:flutter/services.dart';
89
import 'package:http/http.dart' as http;
@@ -142,7 +143,7 @@ Future<void> loadFontIfNecessary(GoogleFontsDescriptor descriptor) async {
142143

143144
// Check if this font can be loaded by the pre-bundled assets.
144145
assetManifest ??= await AssetManifest.loadFromAssetBundle(rootBundle);
145-
final String? assetPath = _findFamilyWithVariantAssetPath(
146+
final String? assetPath = findFamilyWithVariantAssetPath(
146147
descriptor.familyWithVariant,
147148
assetManifest?.listAssets(),
148149
);
@@ -302,21 +303,23 @@ int _computeMatch(GoogleFontsVariant a, GoogleFontsVariant b) {
302303

303304
/// Looks for a matching [familyWithVariant] font, provided the asset manifest.
304305
/// Returns the path of the font asset if found, otherwise an empty string.
305-
String? _findFamilyWithVariantAssetPath(
306+
@visibleForTesting
307+
String? findFamilyWithVariantAssetPath(
306308
GoogleFontsFamilyWithVariant familyWithVariant,
307-
List<String>? manifestValues,
308-
) {
309+
List<String>? manifestValues, {
310+
bool isWeb = kIsWeb,
311+
}) {
309312
if (manifestValues == null) {
310313
return null;
311314
}
312315

313316
final String apiFilenamePrefix = familyWithVariant.toApiFilenamePrefix();
317+
final fileTypes = isWeb
318+
? ['.woff2', '.woff', '.ttf', '.otf']
319+
: ['.ttf', '.otf'];
314320

315321
for (final String asset in manifestValues) {
316-
for (final String matchingSuffix in <String>[
317-
'.ttf',
318-
'.otf',
319-
].where(asset.endsWith)) {
322+
for (final String matchingSuffix in fileTypes.where(asset.endsWith)) {
320323
final String assetWithoutExtension = asset.substring(
321324
0,
322325
asset.length - matchingSuffix.length,

packages/google_fonts/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: google_fonts
22
description: A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.
33
repository: https://github.com/flutter/packages/tree/main/packages/google_fonts
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_fonts%22
5-
version: 7.0.2
5+
version: 7.1.0
66

77
environment:
88
sdk: ^3.9.0
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:google_fonts/src/google_fonts_base.dart';
8+
import 'package:google_fonts/src/google_fonts_family_with_variant.dart';
9+
import 'package:google_fonts/src/google_fonts_variant.dart';
10+
11+
void main() {
12+
group('findFamilyWithVariantAssetPath', () {
13+
const familyWithVariant = GoogleFontsFamilyWithVariant(
14+
family: 'Roboto',
15+
googleFontsVariant: GoogleFontsVariant(
16+
fontWeight: FontWeight.w400,
17+
fontStyle: FontStyle.normal,
18+
),
19+
);
20+
21+
group('common behavior', () {
22+
for (final isWeb in [true, false]) {
23+
test('returns null when manifestValues is null (web: $isWeb)', () {
24+
final String? result = findFamilyWithVariantAssetPath(
25+
familyWithVariant,
26+
null,
27+
isWeb: isWeb,
28+
);
29+
expect(result, isNull);
30+
});
31+
32+
test('returns null when manifestValues is empty (web: $isWeb)', () {
33+
final String? result = findFamilyWithVariantAssetPath(
34+
familyWithVariant,
35+
<String>[],
36+
isWeb: isWeb,
37+
);
38+
expect(result, isNull);
39+
});
40+
41+
test('returns null when font family does not match (web: $isWeb)', () {
42+
final String? result = findFamilyWithVariantAssetPath(
43+
familyWithVariant,
44+
<String>[
45+
'google_fonts/Lato-Regular.ttf',
46+
'google_fonts/OpenSans-Regular.ttf',
47+
],
48+
isWeb: isWeb,
49+
);
50+
expect(result, isNull);
51+
});
52+
53+
test('returns null when variant does not match (web: $isWeb)', () {
54+
final String? result = findFamilyWithVariantAssetPath(
55+
familyWithVariant,
56+
<String>[
57+
'google_fonts/Roboto-Bold.ttf',
58+
'google_fonts/Roboto-Italic.ttf',
59+
],
60+
isWeb: isWeb,
61+
);
62+
expect(result, isNull);
63+
});
64+
65+
test('matches correct variant with multiple fonts (web: $isWeb)', () {
66+
const boldItalicVariant = GoogleFontsFamilyWithVariant(
67+
family: 'Roboto',
68+
googleFontsVariant: GoogleFontsVariant(
69+
fontWeight: FontWeight.w700,
70+
fontStyle: FontStyle.italic,
71+
),
72+
);
73+
final String? result =
74+
findFamilyWithVariantAssetPath(boldItalicVariant, <String>[
75+
'google_fonts/Roboto-Regular.ttf',
76+
'google_fonts/Roboto-Bold.ttf',
77+
'google_fonts/Roboto-BoldItalic.ttf',
78+
'google_fonts/Roboto-Italic.ttf',
79+
], isWeb: isWeb);
80+
expect(result, equals('google_fonts/Roboto-BoldItalic.ttf'));
81+
});
82+
}
83+
});
84+
85+
group('on web', () {
86+
test('supports woff2 format', () {
87+
final String? result = findFamilyWithVariantAssetPath(
88+
familyWithVariant,
89+
<String>['google_fonts/Roboto-Regular.woff2'],
90+
isWeb: true,
91+
);
92+
expect(result, equals('google_fonts/Roboto-Regular.woff2'));
93+
});
94+
95+
test('supports woff format', () {
96+
final String? result = findFamilyWithVariantAssetPath(
97+
familyWithVariant,
98+
<String>['google_fonts/Roboto-Regular.woff'],
99+
isWeb: true,
100+
);
101+
expect(result, equals('google_fonts/Roboto-Regular.woff'));
102+
});
103+
104+
test('supports ttf format', () {
105+
final String? result = findFamilyWithVariantAssetPath(
106+
familyWithVariant,
107+
<String>['google_fonts/Roboto-Regular.ttf'],
108+
isWeb: true,
109+
);
110+
expect(result, equals('google_fonts/Roboto-Regular.ttf'));
111+
});
112+
113+
test('supports otf format', () {
114+
final String? result = findFamilyWithVariantAssetPath(
115+
familyWithVariant,
116+
<String>['google_fonts/Roboto-Regular.otf'],
117+
isWeb: true,
118+
);
119+
expect(result, equals('google_fonts/Roboto-Regular.otf'));
120+
});
121+
122+
test('returns first matching asset in manifest order', () {
123+
// Returns the first asset that matches, regardless of file type
124+
final String? result =
125+
findFamilyWithVariantAssetPath(familyWithVariant, <String>[
126+
'google_fonts/Roboto-Regular.ttf',
127+
'google_fonts/Roboto-Regular.woff2',
128+
'google_fonts/Roboto-Regular.woff',
129+
], isWeb: true);
130+
expect(result, equals('google_fonts/Roboto-Regular.ttf'));
131+
});
132+
133+
test('ignores unsupported file extensions', () {
134+
final String? result =
135+
findFamilyWithVariantAssetPath(familyWithVariant, <String>[
136+
'google_fonts/Roboto-Regular.eot',
137+
'google_fonts/Roboto-Regular.svg',
138+
'google_fonts/Roboto-Regular.woff2',
139+
], isWeb: true);
140+
expect(result, equals('google_fonts/Roboto-Regular.woff2'));
141+
});
142+
});
143+
144+
group('on non-web', () {
145+
test('supports ttf format', () {
146+
final String? result = findFamilyWithVariantAssetPath(
147+
familyWithVariant,
148+
<String>['google_fonts/Roboto-Regular.ttf'],
149+
isWeb: false,
150+
);
151+
expect(result, equals('google_fonts/Roboto-Regular.ttf'));
152+
});
153+
154+
test('supports otf format', () {
155+
final String? result = findFamilyWithVariantAssetPath(
156+
familyWithVariant,
157+
<String>['google_fonts/Roboto-Regular.otf'],
158+
isWeb: false,
159+
);
160+
expect(result, equals('google_fonts/Roboto-Regular.otf'));
161+
});
162+
163+
test('does not select woff2 format', () {
164+
final String? result = findFamilyWithVariantAssetPath(
165+
familyWithVariant,
166+
<String>[
167+
'google_fonts/Roboto-Regular.woff2',
168+
'google_fonts/Roboto-Regular.ttf',
169+
],
170+
isWeb: false,
171+
);
172+
expect(result, equals('google_fonts/Roboto-Regular.ttf'));
173+
});
174+
175+
test('does not select woff format', () {
176+
final String? result = findFamilyWithVariantAssetPath(
177+
familyWithVariant,
178+
<String>[
179+
'google_fonts/Roboto-Regular.woff',
180+
'google_fonts/Roboto-Regular.otf',
181+
],
182+
isWeb: false,
183+
);
184+
expect(result, equals('google_fonts/Roboto-Regular.otf'));
185+
});
186+
});
187+
});
188+
}

0 commit comments

Comments
 (0)