Unverified Commit 85ffa8ce authored by Pierre-Louis's avatar Pierre-Louis Committed by GitHub

[Fonts] Improved icons update script (#88153)

* Improved update_icons.dart

* Handle _

* Update dartdoc

* Rename function

* formatting

* fix type

* Refactor ID generation

* rename

* cleanup

* update comment

* replace typedef with type
parent 338ade81
...@@ -2,7 +2,7 @@ name: dev_tools ...@@ -2,7 +2,7 @@ name: dev_tools
description: Various repository development tools for flutter. description: Various repository development tools for flutter.
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.13.0 <3.0.0"
dependencies: dependencies:
archive: 3.1.2 archive: 3.1.2
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// Regenerates the material icons file. // Regenerates the material icons file.
// See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts-&-Icons // See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts-&-Icons
import 'dart:collection';
import 'dart:convert' show LineSplitter; import 'dart:convert' show LineSplitter;
import 'dart:io'; import 'dart:io';
...@@ -35,71 +36,42 @@ const Map<String, List<String>> _platformAdaptiveIdentifiers = <String, List<Str ...@@ -35,71 +36,42 @@ const Map<String, List<String>> _platformAdaptiveIdentifiers = <String, List<Str
'share': <String>['share', 'ios_share'], 'share': <String>['share', 'ios_share'],
}; };
// Rewrite certain Flutter IDs (reserved keywords, numbers) using exact matching. // Rewrite certain Flutter IDs (numbers) using prefix matching.
const Map<String, String> identifierRewrites = <String, String>{ const Map<String, String> identifierPrefixRewrites = <String, String>{
'1x': 'one_x', '_1': 'one_',
'1x_mobiledata': 'one_x_mobiledata', '_2': 'two_',
'360': 'threesixty', '_3': 'three_',
'2d': 'twod', '_4': 'four_',
'3d': 'threed', '_5': 'five_',
'3d_rotation': 'threed_rotation', '_6': 'six_',
'3p': 'three_p', '_7': 'seven_',
'6_ft': 'six_ft', '_8': 'eight_',
'6_ft_apart': 'six_ft_apart', '_9': 'nine_',
'3g': 'three_g', '_10': 'ten_',
'3g_mobiledata': 'three_g_mobiledata', '_11': 'eleven_',
'4g': 'four_g', '_12': 'twelve_',
'4g_mobiledata': 'four_g_mobiledata', '_13': 'thirteen_',
'4g_plus': 'four_g_plus', '_14': 'fourteen_',
'4g_plus_mobiledata': 'four_g_plus_mobiledata', '_15': 'fifteen_',
'5g': 'five_g', '_16': 'sixteen_',
'30fps': 'thirty_fps', '_17': 'seventeen_',
'30fps_select': 'thirty_fps_select', '_18': 'eighteen_',
'60fps': 'sixty_fps', '_19': 'nineteen_',
'60fps_select': 'sixty_fps_select', '_20': 'twenty_',
'1k': 'one_k', '_21': 'twenty_one_',
'2k': 'two_k', '_22': 'twenty_two_',
'3k': 'three_k', '_23': 'twenty_three_',
'4k': 'four_k', '_24': 'twenty_four_',
'5k': 'five_k', '_30': 'thirty_',
'6k': 'six_k', '_60': 'sixty_',
'7k': 'seven_k', '_360': 'threesixty',
'8k': 'eight_k', '_2d': 'twod',
'9k': 'nine_k', '_3d': 'threed',
'10k': 'ten_k', '_3d_rotation': 'threed_rotation',
'1k_plus': 'one_k_plus', };
'2k_plus': 'two_k_plus',
'3k_plus': 'three_k_plus', // Rewrite certain Flutter IDs (reserved keywords) using exact matching.
'4k_plus': 'four_k_plus', const Map<String, String> identifierExactRewrites = <String, String>{
'5k_plus': 'five_k_plus',
'6k_plus': 'six_k_plus',
'7k_plus': 'seven_k_plus',
'8k_plus': 'eight_k_plus',
'9k_plus': 'nine_k_plus',
'1mp': 'one_mp',
'2mp': 'two_mp',
'3mp': 'three_mp',
'4mp': 'four_mp',
'5mp': 'five_mp',
'6mp': 'six_mp',
'7mp': 'seven_mp',
'8mp': 'eight_mp',
'9mp': 'nine_mp',
'10mp': 'ten_mp',
'11mp': 'eleven_mp',
'12mp': 'twelve_mp',
'13mp': 'thirteen_mp',
'14mp': 'fourteen_mp',
'15mp': 'fifteen_mp',
'16mp': 'sixteen_mp',
'17mp': 'seventeen_mp',
'18mp': 'eighteen_mp',
'19mp': 'nineteen_mp',
'20mp': 'twenty_mp',
'21mp': 'twenty_one_mp',
'22mp': 'twenty_two_mp',
'23mp': 'twenty_three_mp',
'24mp': 'twenty_four_mp',
'class': 'class_', 'class': 'class_',
'new': 'new_', 'new': 'new_',
'switch': 'switch_', 'switch': 'switch_',
...@@ -208,32 +180,38 @@ void main(List<String> args) { ...@@ -208,32 +180,38 @@ void main(List<String> args) {
} }
final String newCodepointsString = newCodepointsFile.readAsStringSync(); final String newCodepointsString = newCodepointsFile.readAsStringSync();
final Map<String, String> newTokenPairMap = stringToTokenPairMap(newCodepointsString); final Map<String, String> newTokenPairMap = _stringToTokenPairMap(newCodepointsString);
final String oldCodepointsString = oldCodepointsFile.readAsStringSync(); final String oldCodepointsString = oldCodepointsFile.readAsStringSync();
final Map<String, String> oldTokenPairMap = stringToTokenPairMap(oldCodepointsString); final Map<String, String> oldTokenPairMap = _stringToTokenPairMap(oldCodepointsString);
_testIsMapSuperset(newTokenPairMap, oldTokenPairMap); _testIsMapSuperset(newTokenPairMap, oldTokenPairMap);
final String iconClassFileData = iconClassFile.readAsStringSync(); final String iconClassFileData = iconClassFile.readAsStringSync();
stderr.writeln('Generating icons file...'); stderr.writeln('Generating icons file...');
final String newIconData = regenerateIconsFile(iconClassFileData, newTokenPairMap); final String newIconData = _regenerateIconsFile(iconClassFileData, newTokenPairMap);
if (argResults[_dryRunOption] as bool) { if (argResults[_dryRunOption] as bool) {
stdout.write(newIconData); stdout.write(newIconData);
} else { } else {
stderr.writeln('\nWriting to ${iconClassFile.path}.'); stderr.writeln('\nWriting to ${iconClassFile.path}.');
iconClassFile.writeAsStringSync(newIconData); iconClassFile.writeAsStringSync(newIconData);
_overwriteOldCodepoints(newCodepointsFile, oldCodepointsFile); _regenerateCodepointsFile(oldCodepointsFile, newTokenPairMap);
} }
} }
ArgResults _handleArguments(List<String> args) { ArgResults _handleArguments(List<String> args) {
final ArgParser argParser = ArgParser() final ArgParser argParser = ArgParser()
..addOption(_newCodepointsPathOption, defaultsTo: _defaultNewCodepointsPath, help: 'Location of the new codepoints directory') ..addOption(_newCodepointsPathOption,
..addOption(_oldCodepointsPathOption, defaultsTo: _defaultOldCodepointsPath, help: 'Location of the existing codepoints directory') defaultsTo: _defaultNewCodepointsPath,
..addOption(_iconsClassPathOption, defaultsTo: _defaultIconsPath, help: 'Location of the material icons file') help: 'Location of the new codepoints directory')
..addOption(_oldCodepointsPathOption,
defaultsTo: _defaultOldCodepointsPath,
help: 'Location of the existing codepoints directory')
..addOption(_iconsClassPathOption,
defaultsTo: _defaultIconsPath,
help: 'Location of the material icons file')
..addFlag(_dryRunOption, defaultsTo: false); ..addFlag(_dryRunOption, defaultsTo: false);
argParser.addFlag('help', abbr: 'h', negatable: false, callback: (bool help) { argParser.addFlag('help', abbr: 'h', negatable: false, callback: (bool help) {
if (help) { if (help) {
...@@ -244,12 +222,12 @@ ArgResults _handleArguments(List<String> args) { ...@@ -244,12 +222,12 @@ ArgResults _handleArguments(List<String> args) {
return argParser.parse(args); return argParser.parse(args);
} }
Map<String, String> stringToTokenPairMap(String codepointData) { Map<String, String> _stringToTokenPairMap(String codepointData) {
final Iterable<String> cleanData = LineSplitter.split(codepointData) final Iterable<String> cleanData = LineSplitter.split(codepointData)
.map((String line) => line.trim()) .map((String line) => line.trim())
.where((String line) => line.isNotEmpty); .where((String line) => line.isNotEmpty);
final Map<String, String> pairs = <String, String>{}; final Map<String, String> pairs = <String,String>{};
for (final String line in cleanData) { for (final String line in cleanData) {
final List<String> tokens = line.split(' '); final List<String> tokens = line.split(' ');
...@@ -262,8 +240,10 @@ Map<String, String> stringToTokenPairMap(String codepointData) { ...@@ -262,8 +240,10 @@ Map<String, String> stringToTokenPairMap(String codepointData) {
return pairs; return pairs;
} }
String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) { String _regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) {
final List<_Icon> newIcons = tokenPairMap.entries.map((MapEntry<String, String> entry) => _Icon(entry)).toList(); final List<_Icon> newIcons = tokenPairMap.entries
.map((MapEntry<String, String> entry) => _Icon(entry))
.toList();
newIcons.sort((_Icon a, _Icon b) => a._compareTo(b)); newIcons.sort((_Icon a, _Icon b) => a._compareTo(b));
final StringBuffer buf = StringBuffer(); final StringBuffer buf = StringBuffer();
...@@ -280,7 +260,7 @@ String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) { ...@@ -280,7 +260,7 @@ String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) {
final List<String> platformAdaptiveDeclarations = <String>[]; final List<String> platformAdaptiveDeclarations = <String>[];
_platformAdaptiveIdentifiers.forEach((String flutterId, List<String> ids) { _platformAdaptiveIdentifiers.forEach((String flutterId, List<String> ids) {
// Automatically finds and generates styled icon declarations. // Automatically finds and generates styled icon declarations.
for (final String style in _Icon.styleSuffixes) { for (final String style in <String>['', '_outlined', '_rounded', '_sharp']) {
try { try {
final _Icon agnosticIcon = newIcons.firstWhere( final _Icon agnosticIcon = newIcons.firstWhere(
(_Icon icon) => icon.id == '${ids[0]}$style', (_Icon icon) => icon.id == '${ids[0]}$style',
...@@ -288,7 +268,6 @@ String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) { ...@@ -288,7 +268,6 @@ String regenerateIconsFile(String iconData, Map<String, String> tokenPairMap) {
final _Icon iOSIcon = newIcons.firstWhere( final _Icon iOSIcon = newIcons.firstWhere(
(_Icon icon) => icon.id == '${ids[1]}$style', (_Icon icon) => icon.id == '${ids[1]}$style',
orElse: () => throw ids[1]); orElse: () => throw ids[1]);
platformAdaptiveDeclarations.add(_Icon.platformAdaptiveDeclaration('$flutterId$style', agnosticIcon, iOSIcon)); platformAdaptiveDeclarations.add(_Icon.platformAdaptiveDeclaration('$flutterId$style', agnosticIcon, iOSIcon));
} catch (e) { } catch (e) {
if (style == '') { if (style == '') {
...@@ -326,7 +305,7 @@ void _testIsMapSuperset(Map<String, String> newCodepoints, Map<String, String> o ...@@ -326,7 +305,7 @@ void _testIsMapSuperset(Map<String, String> newCodepoints, Map<String, String> o
if (!newCodepointsSet.containsAll(oldCodepointsSet)) { if (!newCodepointsSet.containsAll(oldCodepointsSet)) {
stderr.writeln(''' stderr.writeln('''
Error: New codepoints file does not contain all the existing codepoints.\n Error: New codepoints file does not contain all ${oldCodepointsSet.length} existing codepoints.\n
Missing: ${oldCodepointsSet.difference(newCodepointsSet)} Missing: ${oldCodepointsSet.difference(newCodepointsSet)}
''', ''',
); );
...@@ -340,10 +319,13 @@ Error: New codepoints file does not contain all the existing codepoints.\n ...@@ -340,10 +319,13 @@ Error: New codepoints file does not contain all the existing codepoints.\n
} }
} }
// Replace the old codepoints file with the new. void _regenerateCodepointsFile(File oldCodepointsFile, Map<String, String> newTokenPairMap) {
void _overwriteOldCodepoints(File newCodepointsFile, File oldCodepointsFile) { stderr.writeln('Regenerating old codepoints file ${oldCodepointsFile.path}.\n');
stderr.writeln('Copying new codepoints file to ${oldCodepointsFile.path}.\n');
newCodepointsFile.copySync(oldCodepointsFile.path); final StringBuffer buf = StringBuffer();
final SplayTreeMap<String, String> sortedNewTokenPairMap = SplayTreeMap<String, String>.of(newTokenPairMap);
sortedNewTokenPairMap.forEach((String key, String value) => buf.writeln('$key $value'));
oldCodepointsFile.writeAsStringSync(buf.toString());
} }
class _Icon { class _Icon {
...@@ -352,44 +334,63 @@ class _Icon { ...@@ -352,44 +334,63 @@ class _Icon {
id = tokenPair.key; id = tokenPair.key;
hexCodepoint = tokenPair.value; hexCodepoint = tokenPair.value;
// Determine family and htmlSuffix.
if (id.endsWith('_gm_outlined')) {
family = 'GM';
htmlSuffix = '-outlined';
} else if (id.endsWith('_gm_filled')) {
family = 'GM';
htmlSuffix = '-filled';
} else if (id.endsWith('_monoline_outlined')) {
family = 'Monoline';
htmlSuffix = '-outlined';
} else if (id.endsWith('_monoline_filled')) {
family = 'Monoline';
htmlSuffix = '-filled';
} else {
family = 'material';
if (id.endsWith('_outlined') && id != 'insert_chart_outlined') { if (id.endsWith('_outlined') && id != 'insert_chart_outlined') {
shortId = _replaceLast(id, '_outlined');
htmlSuffix = '-outlined'; htmlSuffix = '-outlined';
} else if (id.endsWith('_rounded')) { } else if (id.endsWith('_rounded')) {
shortId = _replaceLast(id, '_rounded');
htmlSuffix = '-round'; htmlSuffix = '-round';
} else if (id.endsWith('_sharp')) { } else if (id.endsWith('_sharp')) {
shortId = _replaceLast(id, '_sharp');
htmlSuffix = '-sharp'; htmlSuffix = '-sharp';
} else { } else {
shortId = id;
htmlSuffix = ''; htmlSuffix = '';
} }
flutterId = id;
for (final MapEntry<String, String> rewritePair in identifierRewrites.entries) {
if (shortId == rewritePair.key) {
flutterId = id.replaceFirst(rewritePair.key, identifierRewrites[rewritePair.key]!);
}
} }
name = id.replaceAll('_', ' '); shortId = _generateShortId(id);
flutterId = generateFlutterId(id);
} }
static const List<String> styleSuffixes = <String>['', '_outlined', '_rounded', '_sharp']; static const List<String> _idSuffixes = <String>[
'_gm_outlined',
'_gm_filled',
'_monoline_outlined',
'_monoline_filled',
'_outlined',
'_rounded',
'_sharp'
];
late String id; // e.g. 5g, 5g_outlined, 5g_rounded, 5g_sharp late String id; // e.g. 5g, 5g_outlined, 5g_rounded, 5g_sharp
late String shortId; // e.g. 5g late String shortId; // e.g. 5g
late String flutterId; // e.g. five_g, five_g_outlined, five_g_rounded, five_g_sharp late String flutterId; // e.g. five_g, five_g_outlined, five_g_rounded, five_g_sharp
late String name; // e.g. five g, five g outlined, five g rounded, five g sharp late String family; // e.g. material
late String hexCodepoint; // e.g. e547 late String hexCodepoint; // e.g. e547
late String htmlSuffix; // The suffix for the 'material-icons' HTML class.
String get name => shortId.replaceAll('_', ' ').trim();
// The suffix for the 'material-icons' HTML class. String get style => htmlSuffix == '' ? '' : ' (${htmlSuffix.replaceFirst('-', '')})';
late String htmlSuffix;
String get mirroredInRTL => _iconsMirroredWhenRTL.contains(shortId) ? ', matchTextDirection: true' : ''; String get dartDoc =>
'<i class="material-icons$htmlSuffix md-36">$shortId</i> &#x2014; $family icon named "$name"$style';
String get dartDoc => '<i class="material-icons$htmlSuffix md-36">$shortId</i> &#x2014; material icon named "$name"'; String get mirroredInRTL => _iconsMirroredWhenRTL.contains(shortId)
? ', matchTextDirection: true'
: '';
String get declaration => String get declaration =>
"static const IconData $flutterId = IconData(0x$hexCodepoint, fontFamily: 'MaterialIcons'$mirroredInRTL);"; "static const IconData $flutterId = IconData(0x$hexCodepoint, fontFamily: 'MaterialIcons'$mirroredInRTL);";
...@@ -411,14 +412,52 @@ class _Icon { ...@@ -411,14 +412,52 @@ class _Icon {
/// Analogous to [String.compareTo] /// Analogous to [String.compareTo]
int _compareTo(_Icon b) { int _compareTo(_Icon b) {
// Sort a regular icon before its variants.
if (shortId == b.shortId) { if (shortId == b.shortId) {
// Sort a regular icon before its variants.
return id.length - b.id.length; return id.length - b.id.length;
} }
return shortId.compareTo(b.shortId); return shortId.compareTo(b.shortId);
} }
String _replaceLast(String string, String toReplace) { static String _replaceLast(String string, String toReplace) {
return string.replaceAll(RegExp('$toReplace\$'), ''); return string.replaceAll(RegExp('$toReplace\$'), '');
} }
static String _generateShortId(String id) {
String shortId = id;
for (final String styleSuffix in _idSuffixes) {
if (styleSuffix == '_outlined' && id == 'insert_chart_outlined')
continue;
shortId = _replaceLast(shortId, styleSuffix);
if (shortId != id) {
break;
}
}
return shortId;
}
/// Given some icon's raw id, returns a valid Dart icon identifier
static String generateFlutterId(String id) {
String flutterId = id;
// Exact identifier rewrites.
for (final MapEntry<String, String> rewritePair
in identifierExactRewrites.entries) {
final String shortId = _Icon._generateShortId(id);
if (shortId == rewritePair.key) {
flutterId = id.replaceFirst(rewritePair.key, identifierExactRewrites[rewritePair.key]!);
}
}
// Prefix identifer rewrites.
for (final MapEntry<String, String> rewritePair
in identifierPrefixRewrites.entries) {
if (id.startsWith(rewritePair.key)) {
flutterId = id.replaceFirst(rewritePair.key, identifierPrefixRewrites[rewritePair.key]!);
}
// TODO(guidezpl): With the next icon update, this won't be necessary, remove it.
if (id.startsWith(rewritePair.key.replaceFirst('_', ''))) {
flutterId = id.replaceFirst(rewritePair.key.replaceFirst('_', ''), identifierPrefixRewrites[rewritePair.key]!);
}
}
return flutterId;
}
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment