diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..639900d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..0d92f55
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1695137929883
+
+
+ 1695137929883
+
+
+
+
+
+
+
+ file://$USER_HOME$/.pub-cache/hosted/pub.dev/args-2.4.2/lib/src/arg_results.dart
+ 64
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/fontify.dart b/bin/fontify.dart
index d67f8e2..5207b4b 100644
--- a/bin/fontify.dart
+++ b/bin/fontify.dart
@@ -3,8 +3,9 @@ import 'dart:io';
import 'package:args/args.dart';
import 'package:fontify/src/cli/arguments.dart';
import 'package:fontify/src/cli/options.dart';
-import 'package:fontify/src/common/api.dart';
+import 'package:fontify/src/common.dart';
import 'package:fontify/src/otf/io.dart';
+import 'package:fontify/src/utils/flutter_class_gen.dart';
import 'package:fontify/src/utils/logger.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
@@ -46,14 +47,12 @@ void _run(CliArguments parsedArgs) {
}
if (parsedArgs.classFile?.existsSync() ?? false) {
- logger.v(
- 'Output file for a Flutter class already exists (${parsedArgs.classFile!.path}) - '
+ logger.v('Output file for a Flutter class already exists (${parsedArgs.classFile!.path}) - '
'overwriting it');
}
if (!parsedArgs.fontFile.existsSync()) {
- logger.v(
- 'Output file for a font file already exists (${parsedArgs.fontFile.path}) - '
+ logger.v('Output file for a font file already exists (${parsedArgs.fontFile.path}) - '
'overwriting it');
}
@@ -63,13 +62,11 @@ void _run(CliArguments parsedArgs) {
.toList();
if (svgFileList.isEmpty) {
- logger.w(
- "The input directory doesn't contain any SVG file (${parsedArgs.svgDir.path}).");
+ logger.w("The input directory doesn't contain any SVG file (${parsedArgs.svgDir.path}).");
}
final svgMap = {
- for (final f in svgFileList)
- p.basenameWithoutExtension(f.path): File(f.path).readAsStringSync(),
+ for (final f in svgFileList) p.basenameWithoutExtension(f.path): File(f.path).readAsStringSync(),
};
final otfResult = svgToOtf(
@@ -88,13 +85,13 @@ void _run(CliArguments parsedArgs) {
final fontFileName = p.basename(parsedArgs.fontFile.path);
final classString = generateFlutterClass(
- glyphList: otfResult.glyphList,
- className: parsedArgs.className,
- indent: parsedArgs.indent,
- fontFileName: fontFileName,
- familyName: otfResult.font.familyName,
- package: parsedArgs.fontPackage,
- );
+ glyphList: otfResult.glyphList,
+ className: parsedArgs.className,
+ indent: parsedArgs.indent,
+ fontFileName: fontFileName,
+ familyName: otfResult.font.familyName,
+ package: parsedArgs.fontPackage,
+ variableNameCase: VariableNameCase.values.firstWhere((e) => e.option == parsedArgs.variableNameCase));
parsedArgs.classFile!.writeAsStringSync(classString);
}
@@ -124,8 +121,7 @@ ${_argParser.usage}
exit(64);
}
-const _kAbout =
- 'Converts .svg icons to an OpenType font and generates Flutter-compatible class.';
+const _kAbout = 'Converts .svg icons to an OpenType font and generates Flutter-compatible class.';
const _kUsage = '''
Usage: fontify [options]
diff --git a/example/example.dart b/example/example.dart
index 4f52e99..8c04c8e 100644
--- a/example/example.dart
+++ b/example/example.dart
@@ -24,6 +24,7 @@ void main() {
familyName: svgToOtfResult.font.familyName,
className: 'MyIcons',
fontFileName: fontFileName,
+ variableNameCase: VariableNameCase.camel,
);
// Writing class content to a file
diff --git a/lib/src/cli/arguments.dart b/lib/src/cli/arguments.dart
index ab74fd6..5330094 100644
--- a/lib/src/cli/arguments.dart
+++ b/lib/src/cli/arguments.dart
@@ -25,6 +25,7 @@ const _kArgAllowedTypes = >{
CliArgument.verbose: [bool],
CliArgument.help: [bool],
CliArgument.configFile: [String],
+ CliArgument.variableNameCase: [String]
};
const kDefaultVerbose = false;
@@ -44,6 +45,7 @@ const kOptionNames = EnumClass({
CliArgument.recursive: 'recursive',
CliArgument.verbose: 'verbose',
+ CliArgument.variableNameCase: 'variable-name-case',
CliArgument.help: 'help',
CliArgument.configFile: 'config-file',
@@ -61,6 +63,7 @@ const kConfigKeys = EnumClass({
CliArgument.fontName: 'font_name',
CliArgument.normalize: 'normalize',
CliArgument.ignoreShapes: 'ignore_shapes',
+ CliArgument.variableNameCase: 'variable_name_case',
CliArgument.recursive: 'recursive',
CliArgument.verbose: 'verbose',
@@ -92,6 +95,7 @@ enum CliArgument {
// Others
recursive,
verbose,
+ variableNameCase,
// Only in CLI
help,
@@ -113,6 +117,7 @@ class CliArguments {
this.normalize,
this.verbose,
this.configFile,
+ this.variableNameCase,
);
/// Creates [CliArguments] for a map of raw values.
@@ -135,6 +140,7 @@ class CliArguments {
map[CliArgument.normalize] as bool?,
map[CliArgument.verbose] as bool?,
map[CliArgument.configFile] as File?,
+ map[CliArgument.variableNameCase] as String?,
);
}
@@ -150,6 +156,7 @@ class CliArguments {
final bool? normalize;
final bool? verbose;
final File? configFile;
+ final String? variableNameCase;
}
/// Parses argument list.
@@ -157,8 +164,7 @@ class CliArguments {
/// Throws [CliHelpException], if 'help' option is present.
///
/// Returns an instance of [CliArguments] containing all parsed data.
-Map parseArguments(
- ArgParser argParser, List args) {
+Map parseArguments(ArgParser argParser, List args) {
late final ArgResults argResults;
try {
argResults = argParser.parse(args);
@@ -170,13 +176,11 @@ Map parseArguments(
throw CliHelpException();
}
- final posArgsLength =
- math.min(_kPositionalArguments.length, argResults.rest.length);
+ final posArgsLength = math.min(_kPositionalArguments.length, argResults.rest.length);
final rawArgMap = {
for (final e in kOptionNames.entries) e.key: argResults[e.value],
- for (var i = 0; i < posArgsLength; i++)
- _kPositionalArguments[i]: argResults.rest[i],
+ for (var i = 0; i < posArgsLength; i++) _kPositionalArguments[i]: argResults.rest[i],
};
return rawArgMap;
@@ -219,9 +223,7 @@ Map? parseConfig(String config) {
return null;
}
- final entries = fontifyYamlMap.entries
- .map(_mapConfigKeyEntry)
- .whereType>();
+ final entries = fontifyYamlMap.entries.map(_mapConfigKeyEntry).whereType>();
return Map.fromEntries(entries);
}
@@ -235,10 +237,7 @@ CliArguments parseArgsAndConfig(ArgParser argParser, List args) {
var parsedArgs = parseArguments(argParser, args);
final dynamic configFile = parsedArgs[CliArgument.configFile];
- final configList = [
- if (configFile is String) configFile,
- ..._kDefaultConfigPathList
- ].map((e) => File(e));
+ final configList = [if (configFile is String) configFile, ..._kDefaultConfigPathList].map((e) => File(e));
for (final configFile in configList) {
if (configFile.existsSync()) {
@@ -304,13 +303,11 @@ extension CliArgumentMapExtension on Map {
}
if (svgDir.statSync().type != FileSystemEntityType.directory) {
- throw CliArgumentException(
- "The input directory is not a directory or it doesn't exist.");
+ throw CliArgumentException("The input directory is not a directory or it doesn't exist.");
}
if (indent != null && indent < 0) {
- throw CliArgumentException(
- 'indent must be a non-negative integer, was $indent.');
+ throw CliArgumentException('indent must be a non-negative integer, was $indent.');
}
}
diff --git a/lib/src/cli/options.dart b/lib/src/cli/options.dart
index 8ef3427..03d0340 100644
--- a/lib/src/cli/options.dart
+++ b/lib/src/cli/options.dart
@@ -1,5 +1,6 @@
import 'package:args/args.dart';
+import '../../fontify.dart';
import 'arguments.dart';
void defineOptions(ArgParser argParser) {
@@ -8,8 +9,7 @@ void defineOptions(ArgParser argParser) {
..addOption(
kOptionNames[CliArgument.classFile]!,
abbr: 'o',
- help:
- 'Output path for Flutter-compatible class that contains identifiers for the icons.',
+ help: 'Output path for Flutter-compatible class that contains identifiers for the icons.',
valueHelp: 'path',
)
..addOption(
@@ -28,10 +28,17 @@ void defineOptions(ArgParser argParser) {
..addOption(
kOptionNames[CliArgument.fontPackage]!,
abbr: 'p',
- help:
- 'Name of a package that provides a font. Used to provide a font through package dependency.',
+ help: 'Name of a package that provides a font. Used to provide a font through package dependency.',
valueHelp: 'name',
)
+ ..addOption(
+ kOptionNames[CliArgument.variableNameCase]!,
+ abbr: 'n',
+ help: 'The case to use when generating variable names.',
+ valueHelp: 'name',
+ allowed: VariableNameCase.values.map((e) => e.option),
+ defaultsTo: VariableNameCase.camel.option,
+ )
..addSeparator('Font options:')
..addOption(
kOptionNames[CliArgument.fontName]!,
@@ -41,8 +48,7 @@ void defineOptions(ArgParser argParser) {
)
..addFlag(
kOptionNames[CliArgument.normalize]!,
- help:
- 'Enables glyph normalization for the font. Disable this if every icon has the same size and positioning.',
+ help: 'Enables glyph normalization for the font. Disable this if every icon has the same size and positioning.',
defaultsTo: true,
)
..addFlag(
@@ -54,8 +60,7 @@ void defineOptions(ArgParser argParser) {
..addOption(
kOptionNames[CliArgument.configFile]!,
abbr: 'z',
- help:
- 'Path to Fontify yaml configuration file. pubspec.yaml and fontify.yaml files are used by default.',
+ help: 'Path to Fontify yaml configuration file. pubspec.yaml and fontify.yaml files are used by default.',
valueHelp: 'path',
)
..addFlag(
diff --git a/lib/src/common/api.dart b/lib/src/common/api.dart
index 4e976f9..d1b94f9 100644
--- a/lib/src/common/api.dart
+++ b/lib/src/common/api.dart
@@ -35,10 +35,7 @@ SvgToOtfResult svgToOtf({
}) {
normalize ??= true;
- final svgList = [
- for (final e in svgMap.entries)
- Svg.parse(e.key, e.value, ignoreShapes: ignoreShapes)
- ];
+ final svgList = [for (final e in svgMap.entries) Svg.parse(e.key, e.value, ignoreShapes: ignoreShapes)];
if (!normalize) {
for (var i = 1; i < svgList.length; i++) {
@@ -74,6 +71,7 @@ SvgToOtfResult svgToOtf({
/// * [package] is the name of a font package. Used to provide a font through package dependency.
/// * [fontFileName] is font file's name. Used in generated docs for class.
/// * [indent] is a number of spaces in leading indentation for class' members. Defaults to 2.
+/// * [variableNameCase] is the generated variable name case (e.g. camelCase, snake_case)
///
/// Returns content of a class file.
String generateFlutterClass({
@@ -83,6 +81,7 @@ String generateFlutterClass({
String? fontFileName,
String? package,
int? indent,
+ VariableNameCase variableNameCase = VariableNameCase.camel,
}) {
final generator = FlutterClassGenerator(
glyphList,
@@ -91,6 +90,7 @@ String generateFlutterClass({
fontFileName: fontFileName,
familyName: familyName,
package: package,
+ variableNameCase: variableNameCase,
);
return generator.generate();
diff --git a/lib/src/otf/table/maxp.dart b/lib/src/otf/table/maxp.dart
index 61c6d8a..8d4d03b 100644
--- a/lib/src/otf/table/maxp.dart
+++ b/lib/src/otf/table/maxp.dart
@@ -2,7 +2,6 @@ import 'dart:typed_data';
import '../../utils/otf.dart';
import '../debugger.dart';
-
import 'abstract.dart';
import 'glyf.dart';
import 'table_record_entry.dart';
@@ -62,7 +61,7 @@ class MaximumProfileTable extends FontTable {
return MaximumProfileTable.v1(
null,
numGlyphs,
- glyf!.maxPoints,
+ glyf.maxPoints,
glyf.maxContours,
0, // Composite glyphs are not supported
0, // Composite glyphs are not supported
@@ -79,8 +78,7 @@ class MaximumProfileTable extends FontTable {
0);
}
- static MaximumProfileTable? fromByteData(
- ByteData data, TableRecordEntry entry) {
+ static MaximumProfileTable? fromByteData(ByteData data, TableRecordEntry entry) {
final version = data.getInt32(entry.offset);
if (version == _kVersion0) {
diff --git a/lib/src/utils/flutter_class_gen.dart b/lib/src/utils/flutter_class_gen.dart
index 9527e81..2c8b509 100644
--- a/lib/src/utils/flutter_class_gen.dart
+++ b/lib/src/utils/flutter_class_gen.dart
@@ -1,5 +1,5 @@
-import 'package:recase/recase.dart';
import 'package:path/path.dart' as p;
+import 'package:recase/recase.dart';
import '../common/constant.dart';
import '../common/generic_glyph.dart';
@@ -26,6 +26,7 @@ class FlutterClassGenerator {
/// * [package] is the name of a font package. Used to provide a font through package dependency.
/// * [fontFileName] is font file's name. Used in generated docs for class.
/// * [indent] is a number of spaces in leading indentation for class' members. Defaults to 2.
+ /// * [variableNameCase] is the generated variable name case (e.g. camelCase, snake_case)
FlutterClassGenerator(
this.glyphList, {
String? className,
@@ -33,11 +34,12 @@ class FlutterClassGenerator {
String? fontFileName,
String? package,
int? indent,
+ VariableNameCase variableNameCase = VariableNameCase.camel,
}) : _indent = ' ' * (indent ?? _kDefaultIndent),
_className = _getVarName(className ?? _kDefaultClassName),
_familyName = familyName ?? kDefaultFontFamily,
_fontFileName = fontFileName ?? _kDefaultFontFileName,
- _iconVarNames = _generateVariableNames(glyphList),
+ _iconVarNames = _generateVariableNames(glyphList, variableNameCase),
_package = package?.isEmpty ?? true ? null : package;
final List glyphList;
@@ -48,12 +50,12 @@ class FlutterClassGenerator {
final String? _package;
final List _iconVarNames;
- static List _generateVariableNames(List glyphList) {
+ static List _generateVariableNames(List glyphList, VariableNameCase variableNameCase) {
final iconNameSet = {};
return glyphList.map((g) {
- final baseName =
- _getVarName(p.basenameWithoutExtension(g.metadata.name!)).snakeCase;
+ final rc = ReCase(_getVarName(p.basenameWithoutExtension(g.metadata.name!)));
+ final baseName = variableNameCase == VariableNameCase.camel ? rc.camelCase : rc.snakeCase;
final usingDefaultName = baseName.isEmpty;
var variableName = usingDefaultName ? _kUnnamedIconName : baseName;
@@ -74,8 +76,7 @@ class FlutterClassGenerator {
String variableNameWithCount;
do {
- variableNameWithCount =
- '${variableWithoutCount}_${++variableNameCount}';
+ variableNameWithCount = '${variableWithoutCount}_${++variableNameCount}';
} while (iconNameSet.contains(variableNameWithCount));
variableName = variableNameWithCount;
@@ -89,8 +90,7 @@ class FlutterClassGenerator {
bool get _hasPackage => _package != null;
- String get _fontFamilyConst =>
- "static const iconFontFamily = '$_familyName';";
+ String get _fontFamilyConst => "static const iconFontFamily = '$_familyName';";
String get _fontPackageConst => "static const iconFontPackage = '$_package';";
@@ -103,18 +103,11 @@ class FlutterClassGenerator {
final varName = _iconVarNames[index];
final hexCode = charCode.toRadixString(16);
- final posParamList = [
- 'fontFamily: iconFontFamily',
- if (_hasPackage) 'fontPackage: iconFontPackage'
- ];
+ final posParamList = ['fontFamily: iconFontFamily', if (_hasPackage) 'fontPackage: iconFontPackage'];
final posParamString = posParamList.join(', ');
- return [
- '',
- '/// $iconName',
- 'static const IconData $varName = IconData(0x$hexCode, $posParamString);'
- ];
+ return ['', '/// $iconName', 'static const IconData $varName = IconData(0x$hexCode, $posParamString);'];
}
/// Generates content for a class' file.
@@ -127,8 +120,15 @@ class FlutterClassGenerator {
for (var i = 0; i < glyphList.length; i++) ..._generateIconConst(i),
];
- final classContentString =
- classContent.map((e) => e.isEmpty ? '' : '$_indent$e').join('\n');
+ var classContentString = classContent.map((e) => e.isEmpty ? '' : '$_indent$e').join('\n');
+
+ classContentString += '\n\n${_indent}static const Map allIcons = {\n';
+
+ for (final name in _iconVarNames) {
+ classContentString += "${_indent}${_indent}'$name': $name,\n";
+ }
+
+ classContentString += '$_indent};';
return '''
// Generated code: do not hand-edit.
@@ -162,3 +162,12 @@ $classContentString
''';
}
}
+
+/// Represents the case of the variable names.
+enum VariableNameCase {
+ camel(option: 'camel'),
+ snake(option: 'snake');
+
+ const VariableNameCase({required this.option});
+ final String option;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1ca1ef4..d033d93 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -6,22 +6,21 @@ description: >-
homepage: https://github.com/westracer/fontify
environment:
- sdk: ">=2.12.0 <3.0.0"
+ sdk: '>=3.0.0 <4.0.0'
dependencies:
args: ^2.0.0
collection: ^1.15.0
- logger: ^1.0.0
+ logger: ^1.3.0
meta: ^1.3.0
path: ^1.8.0
- path_parsing: ^0.2.0
+ path_parsing: ^1.0.1
recase: ^4.0.0
vector_math: ^2.1.0
- xml: ^5.1.0
+ xml: ^6.3.0
yaml: ^3.1.0
dev_dependencies:
- pedantic: ^1.11.0
test: ^1.17.0
executables:
diff --git a/test/cli_test.dart b/test/cli_test.dart
index 0da70fd..245e0b0 100644
--- a/test/cli_test.dart
+++ b/test/cli_test.dart
@@ -12,8 +12,7 @@ void main() {
defineOptions(_argParser);
void expectCliArgumentException(List args) {
- expect(() => parseArgsAndConfig(_argParser, args),
- throwsA(const TypeMatcher()));
+ expect(() => parseArgsAndConfig(_argParser, args), throwsA(const TypeMatcher()));
}
test('No positional args', () {
@@ -52,6 +51,7 @@ void main() {
'--verbose',
'--config-file=test/config.yaml',
'--package=test_package',
+ '--variable-name-case=snake'
];
final parsedArgs = parseArgsAndConfig(_argParser, args);
@@ -68,6 +68,7 @@ void main() {
expect(parsedArgs.verbose, isTrue);
expect(parsedArgs.configFile?.path, 'test/config.yaml');
expect(parsedArgs.fontPackage, 'test_package');
+ expect(parsedArgs.variableNameCase, 'snake');
});
test('All arguments with defaults', () {
@@ -96,8 +97,7 @@ void main() {
test('Help', () {
void expectCliHelpException(List args) {
- expect(() => parseArgsAndConfig(_argParser, args),
- throwsA(const TypeMatcher()));
+ expect(() => parseArgsAndConfig(_argParser, args), throwsA(const TypeMatcher()));
}
expectCliHelpException(['-h']);
@@ -186,8 +186,7 @@ void main() {
}
void expectCliArgumentException(String cfg) {
- expect(() => _parseConfig(cfg),
- throwsA(const TypeMatcher()));
+ expect(() => _parseConfig(cfg), throwsA(const TypeMatcher()));
}
test('No required', () {