Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Prioritize split font fallbacks over monolythic ones. #56187

Merged
merged 8 commits into from
Oct 29, 2024
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 DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,7 @@ deps = {
'packages': [
{
'package': 'flutter/flutter_font_fallbacks',
'version': '91f2de34d4a309816a182ce7552201d95388e2e0bceea951a6dd04693ed6fe30'
'version': '0570a0ea824083da79de07b131fc5df7f55dad4d6cba2ee704912ce7e4ce812a'
}
],
'dep_type': 'cipd',
Expand Down
80 changes: 43 additions & 37 deletions lib/web_ui/dev/roll_fallback_fonts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,20 @@ class RollFallbackFontsCommand extends Command<bool>

Future<void> _generateFallbackFontData() async {
final http.Client client = http.Client();
final List<String> fallbackFonts = <String>[];
final Map<String, Uri> urlForFamily = <String, Uri>{};
await _addApiFallbackFonts(client, fallbackFonts, urlForFamily);
await _addSplitFallbackFonts(client, fallbackFonts, urlForFamily);
final List<_FontInfo> fallbackFontInfo = <_FontInfo>[
...await _processSplitFallbackFonts(client, splitFallbackFonts),
...await _processFallbackFonts(client, apiFallbackFonts),
];
final List<_Font> fallbackFontData = <_Font>[];

final Map<String, String> charsetForFamily = <String, String>{};
final io.Directory fontDir = await io.Directory.systemTemp.createTemp('flutter_fallback_fonts');
print('Downloading fonts into temp directory: ${fontDir.path}');
final AccumulatorSink<crypto.Digest> hashSink = AccumulatorSink<crypto.Digest>();
final ByteConversionSink hasher = crypto.sha256.startChunkedConversion(hashSink);
for (final String family in fallbackFonts) {

for (final (:family, :uri) in fallbackFontInfo) {
print('Downloading $family...');
final Uri? uri = urlForFamily[family];
if (uri == null) {
throw ToolExit('Unable to determine URL to download $family. '
'Check if it is still hosted on Google Fonts.');
}
final http.Response fontResponse = await client.get(uri);
if (fontResponse.statusCode != 200) {
throw ToolExit('Failed to download font for $family');
Expand Down Expand Up @@ -112,12 +109,11 @@ class RollFallbackFontsCommand extends Command<bool>

final StringBuffer sb = StringBuffer();

final List<_Font> fonts = <_Font>[];

for (final String family in fallbackFonts) {
int index = 0;
for (final _FontInfo fontInfo in fallbackFontInfo) {
final List<int> starts = <int>[];
final List<int> ends = <int>[];
final String charset = charsetForFamily[family]!;
final String charset = charsetForFamily[fontInfo.family]!;
for (final String range in charset.split(' ')) {
// Range is one hexadecimal number or two, separated by `-`.
final List<String> parts = range.split('-');
Expand All @@ -130,10 +126,10 @@ class RollFallbackFontsCommand extends Command<bool>
ends.add(last);
}

fonts.add(_Font(family, fonts.length, starts, ends));
fallbackFontData.add(_Font(fontInfo, index++, starts, ends));
}

final String fontSetsCode = _computeEncodedFontSets(fonts);
final String fontSetsCode = _computeEncodedFontSets(fallbackFontData);

sb.writeln('// Copyright 2013 The Flutter Authors. All rights reserved.');
sb.writeln('// Use of this source code is governed by a BSD-style license '
Expand All @@ -146,9 +142,9 @@ class RollFallbackFontsCommand extends Command<bool>
sb.writeln();
sb.writeln('List<NotoFont> getFallbackFontList() => <NotoFont>[');

for (final _Font font in fonts) {
final String family = font.family;
final String urlString = urlForFamily[family]!.toString();
for (final _Font font in fallbackFontData) {
final String family = font.info.family;
final String urlString = font.info.uri.toString();
if (!urlString.startsWith(expectedUrlPrefix)) {
throw ToolExit(
'Unexpected url format received from Google Fonts API: $urlString.');
Expand Down Expand Up @@ -307,14 +303,14 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
]);
}

Future<void> _addApiFallbackFonts(
Future<List<_FontInfo>> _processFallbackFonts(
http.Client client,
List<String> fallbackFonts,
Map<String, Uri> urlForFamily,
List<String> requestedFonts,
) async {
if (apiKey.isEmpty) {
throw UsageException('No Google Fonts API key provided', argParser.usage);
}
final List<_FontInfo> processedFonts = <_FontInfo>[];
final http.Response response = await client.get(Uri.parse(
'https://www.googleapis.com/webfonts/v1/webfonts?key=$apiKey'));
if (response.statusCode != 200) {
Expand All @@ -327,22 +323,25 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
.cast<Map<String, dynamic>>();
for (final Map<String, Object?> fontData in fontDatas) {
final String family = fontData['family']! as String;
if (apiFallbackFonts.contains(family)) {
if (requestedFonts.contains(family)) {
final files = fontData['files']! as Map<String, Object?>;
final Uri uri = Uri.parse(files['regular']! as String)
.replace(scheme: 'https');
urlForFamily[family] = uri;
fallbackFonts.add(family);
processedFonts.add((
family: family,
uri: uri,
));
}
}
return processedFonts;
}

Future<void> _addSplitFallbackFonts(
Future<List<_FontInfo>> _processSplitFallbackFonts(
http.Client client,
List<String> fallbackFonts,
Map<String, Uri> urlForFamily,
List<String> requestedFonts,
) async {
for (final String font in splitFallbackFonts) {
final List<_FontInfo> processedFonts = <_FontInfo>[];
for (final String font in requestedFonts) {
final String modifiedFontName = font.replaceAll(' ', '+');
final Uri cssUri = Uri.parse(
'https://fonts.googleapis.com/css2?family=$modifiedFontName');
Expand All @@ -358,13 +357,17 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
final UriCollector uriCollector = UriCollector();
stylesheet.visit(uriCollector);
int familyCount = 0;
// Give each font shard a unique family name.
for (final Uri uri in uriCollector.uris) {
final String fontName = '$font $familyCount';
fallbackFonts.add(fontName);
urlForFamily[fontName] = uri;
final String family = '$font $familyCount';
processedFonts.add((
family: family,
uri: uri,
));
familyCount += 1;
}
}
return processedFonts;
}
}

Expand Down Expand Up @@ -551,10 +554,13 @@ Future<bool> _checkForLicenseAttribution(
return licenseString.contains(attributionString);
}

// The basic information of a font: its [family] (name) and [uri].
typedef _FontInfo = ({String family, Uri uri});

class _Font {
_Font(this.family, this.index, this.starts, this.ends);
_Font(this.info, this.index, this.starts, this.ends);

final String family;
final _FontInfo info;
final int index;
final List<int> starts;
final List<int> ends; // inclusive ends
Expand All @@ -566,9 +572,9 @@ class _Font {
String.fromCharCodes(
'$index'.codeUnits.map((int ch) => ch - 48 + 0x2080));

String get _shortName => family.startsWith('Noto Sans ')
? family.substring('Noto Sans '.length)
: family;
String get _shortName => info.family.startsWith('Noto Sans ')
? info.family.substring('Noto Sans '.length)
: info.family;
}

/// The boundary of a range of a font.
Expand Down
Loading