首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 文本解读 9 | 打造 Icon 图标字体创建工具

Flutter 文本解读 9 | 打造 Icon 图标字体创建工具

作者头像
张风捷特烈
发布2021-01-27 15:34:06
8080
发布2021-01-27 15:34:06
举报

@charset "UTF-8";.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1:first-child,.markdown-body h2:first-child,.markdown-body h3:first-child,.markdown-body h4:first-child,.markdown-body h5:first-child,.markdown-body h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.markdown-body h1:before,.markdown-body h2:before,.markdown-body h3:before,.markdown-body h4:before,.markdown-body h5:before,.markdown-body h6:before{content:"#";display:inline-block;color:#3eaf7c;padding-right:.23em}.markdown-body h1{position:relative;font-size:2.5rem;margin-bottom:5px}.markdown-body h1:before{font-size:2.5rem}.markdown-body h2{padding-bottom:.5rem;font-size:2.2rem;border-bottom:1px solid #ececec}.markdown-body h3{font-size:1.5rem;padding-bottom:0}.markdown-body h4{font-size:1.25rem}.markdown-body h5{font-size:1rem}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body strong{color:#3eaf7c}.markdown-body img{max-width:100%;border-radius:2px;display:block;margin:auto;border:3px solid rgba(62,175,124,.2)}.markdown-body hr{border:none;border-top:1px solid #3eaf7c;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;overflow-x:auto;padding:.2rem .5rem;margin:0;color:#3eaf7c;font-weight:700;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75;border-radius:6px;border:2px solid #3eaf7c}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{font-weight:500;text-decoration:none;color:#3eaf7c}.markdown-body a:active,.markdown-body a:hover{border-bottom:1.5px solid #3eaf7c}.markdown-body a:before{content:"⇲"}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #3eaf7c}.markdown-body thead{background:#3eaf7c;color:#fff;text-align:left}.markdown-body tr:nth-child(2n){background-color:rgba(62,175,124,.2)}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:.5rem solid;border-color:#42b983;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body details{outline:none;border:none;border-left:4px solid #3eaf7c;padding-left:10px;margin-left:4px}.markdown-body details summary{cursor:pointer;border:none;outline:none;background:#fff;margin:0 -17px}.markdown-body details summary::-webkit-details-marker{color:#3eaf7c}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body ol li::marker{color:#3eaf7c}.markdown-body ul li{list-style:none}.markdown-body ul li:before{content:"•";margin-right:4px;color:#3eaf7c}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}

零、前言
1. 前情简介

上一节写了一个小工具,通过 icon_builder.dart 来自动生成对应图标相关的 dart 文件。这样我们从引用自定义的图标只需要: 下载 -> 拷贝-> 生成

现在为止,功能还是比较单薄的,比如字体还需要自己在 pubspec.yaml 中配置,其实作为一个脚本而言,最好的就是一键 OK,所以 pubspec.yaml 中配置也可以通过代码自动完成。再比如说,多个字体图标文件怎么办,如何能更方便地支持多图标字体。


2.本系列其他文章

一、 pubspec.yaml 中配置自动生成
1.需求分析
1. 如果没有 fonts: 节点,则创建 fonts: 节点
2. 在 [ pubspec.yaml ] 中自动对 fonts: 节点进行字体图标配置
3. 如果已存在 该字体图标配置 ,则不处理

2.分析 pubspec.yaml

首先说说思路,pubspec.yaml 是一行行配置的,所以我们可以读行。寻找到 fonts 行,看看有没有 该字体图标配置,如果没有,则在 fonts 行的下一行添加对应节点,最后将字符串行列表写回 pubspec.yaml 即可。那么寻找 fonts 呢?也许你会想:用 contains 不就行了吗。但这样的匹配并不精确,可以会误判而出问题,匹配最好使用正则。通过 ^ fonts\: 就可以匹配到以它开头的字符。


为了避免注释对匹配的干扰,在处理时,通过 RegExp(r'#.*') 将行中的注释临时去掉。fontLinefamilyLine 分别记录 fonts该字体图标配置 对应的行索引。

void handleYaml(
    {String family = 'TolyIcon',
    String asset = 'assets/iconfont/iconfont.ttf'}) async {
  File yamlFile = File(path.join(Directory.current.path, 'pubspec.yaml'));
  List<String> yamlLines = await yamlFile.readAsLines();
  RegExp fontsReg = RegExp(r'^  fonts\:');
  RegExp familyReg = RegExp(r'\- family:.*' + family);
  RegExp commentReg = RegExp(r'#.*');

  int fontLine = -1;
  int familyLine = -1;
  for (int i = 0; i < yamlLines.length; i++) {
    // 去除注释
    String pureLine = yamlLines[i].replaceAll(commentReg, '');
    if (fontsReg.hasMatch(pureLine)) {
      fontLine = i;
    }
    if (familyReg.hasMatch(pureLine)) {
      familyLine = i;
    }
  }
  print('fontLine:$fontLine-----------familyLine:$familyLine---------',);
}
复制代码

如下处理,当 fontLine == -1,则表示 fonts: 节点 不存在,则添加 fonts: 节点和配置。familyLine == -1, 则表示 配置不存在,则添加配置。否则,不处理。

  String config =
"""
    - family: $family
      fonts:
        - asset: $asset""";

  if(fontLine == -1){
    // fontLine 不存在,则添加 fonts: 节点和配置
    yamlLines.add('  fonts: ');
    yamlLines.add( config );
  }else{
    if(familyLine == -1){
      // familyLine 不存在,则添加配置
      yamlLines.insert(fontLine + 1, config);
    }else{
      // 否则说明该图标字体已配置,无须处理
      return;
    }
  }
  await yamlFile.writeAsString(yamlLines.join('\n'));
复制代码

这样,在 icon_builder.dart 运行后,pubspec.yaml 就会自动把图标字体节点配置好。


3.可配置参数

可以将 字体名字体资源文件夹产出位置 作为配置的参数。这样可以提取一个 buildAnIconFont 方法用于构建一个 字体图标 文件。

main() async {
  String cssPath = 'assets/iconfont/iconfont.css'; // 样式路径
  String fontPath = 'assets/iconfont/iconfont.ttf'; // 字体路径
  String fontName = 'TolyIcon'; // 字体名称
  String dist = 'generate/icon'; //输出文件地址

  await buildAnIconFont(cssPath, fontPath, fontName, dist);
}

注意一点,.css样式文件在生成 .dart 文件后,其使命就完成了,可以删除。

Future<void> buildAnIconFont(String fontDir, String fontName, String dist) async {
  String asset = '$fontDir/$fontName.ttf'; //输出文件地址
  File target = File(path.join(Directory.current.path, fontDir, '$fontName.css'));
  if(!target.existsSync()) return; // 样式文件不存在,则直接返回
  
  String str = await target.readAsString();
  List<String> names = [];
  List<String> unicodes = [];
  StringScanner _scanner = StringScanner(str);

  while (!_scanner.isDone) {
    if (_scanner.scan(RegExp(r'\.icon-(.*?):'))) {
      String word = _scanner.lastMatch[1];
      names.add(word);
    }

    if (_scanner.scan(RegExp(r'"\\(.*?)"'))) {
      String word = _scanner.lastMatch[1];
      unicodes.add(word);
    }

    if (!_scanner.isDone) {
      _scanner.position++;
    }
  }

  assert(names.length == unicodes.length);

  Map<String, String> iconMap = Map.fromIterables(names, unicodes);
  String code = getCode(iconMap, fontName: fontName);
  await save2File(code, filePath: dist, fontName: fontName);
  await handleYaml(family: fontName, asset: asset);
  await target.delete(); // 删除样式文件
}
复制代码

二、 多个字体图标文件处理
1.多图标字体分析

其实在图标网站可以通过项目 来管理图标,一般一个项目一个图标文件就够了。但如果真的有多个图标文件的需求,也可以将 icon_builder.dart 再优化一些。


就目前的小工具而言,再引入一个 Ruby 的字体文件,构建一下。也可以自动生成对应的 .dart 文件,以及自动配置 fonts 节点。

不过还需要手动修改些配置,有一丢丢的小麻烦。想要不麻烦,那就用规范来减少配置。现在要求 .css 和 .ttf 的文件名相同,且文件名即为字体名。这样就可以遍历文件夹,解析文件名,从而减少配置。


2.代码处理

多字体文件放置如下,只需要配置资源目录输出目录 即可。

main() {
  String resDir = 'assets/iconfont'; // 字体位置
  String dist = 'generate/icon'; //输出文件地址
  parserResDir(resDir, dist);
}

void parserResDir(String resDir,String dist) async{
  Directory dir = Directory(path.join(Directory.current.path, resDir));
  List files = dir.listSync();

  for(int i = 0; i < files.length ; i++){
    File file = files[i];
    if (file is File && file.path.endsWith('.css')) {
      String fontName = path.basenameWithoutExtension(file.path);
      await buildAnIconFont(resDir,fontName, dist);
    }
  }
}

这样运行 icon_builder.dart 过后,1. css 文件会被删除;2. 相应的.dart 文件会自动生成;3. pubspec.yaml 会自动配置。可以说已经很不错了。


3.字体类的融合

如果想要使用两种字体,但只想通过一个类进行调用,这样就不会生成过多的类,使用起来方便些。其实处理起来也很简单,设置两个标识,用于是否开启 mergeClass 以及融合后的类名。融合后效果如下,两个字体通过一个 .dart 文件管理。

bool mergeClass = true;
String className = 'TolyIcon';

这样就可以通过一个类,同时使用多个字体文件:

image-20210123212746791
image-20210123212746791
Wrap(
  spacing: 20, 
  children: [
    Icon(TolyIcon.icon_collect, size: 50,),
    Icon(TolyIcon.icon_ruby, size: 50,)
]);

三、icon_builder.dart 完整代码

代码一共也就 170 行,但功能还不错。随便写写,代码结构上有待优化,其中包含了很多文件处理,字符串分析的知识,这些都挺好玩的。有什么更好的想法,也可以和我在群里交流。其实按照这个逻辑做成 AS 插件Gradle 插件也未尝不可。不过通过一个小脚本也比较方便,运行一下就 OK 了 。谢谢观看 ~

import 'dart:io';
import 'package:string_scanner/string_scanner.dart';
import 'package:path/path.dart' as path;

/// create by 张风捷特烈 on 2021/1/22
/// contact me by email 1981462002@qq.com
/// 说明: iconfont 解析构造器

bool deleteCss = true; // 是否删除 css
bool mergeClass = true; // 多个字体文件时是否融合成一个类
String className = 'TolyIcon'; // 融合成一个类时类名
String resDir = 'assets/iconfont'; //资源文件地址
String dist = 'generate/icon'; //输出文件地址

main() async {
  File target = File(path.join(Directory.current.path, 'lib', dist, '$className.dart'));
  if(mergeClass&&target.existsSync()) {
    await target.writeAsString('');
  }

  await parserResDir(resDir, dist);

  if (mergeClass) {
    String content = await target.readAsString();
    String result = """import 'package:flutter/widgets.dart';
//Power By 张风捷特烈 --- Generated file. Do not edit.

class $className {
    $className._();
""";
    result += content;
    result += "}";
    await target.writeAsString(result);
  }
}

Future<void> parserResDir(String resDir, String dist) async {
  Directory dir = Directory(path.join(Directory.current.path, resDir));
  List files = dir.listSync();

  for (int i = 0; i < files.length; i++) {
    File file = files[i];
    if (file is File && file.path.endsWith('.css')) {
      String fontName = path.basenameWithoutExtension(file.path);
      await buildAnIconFont(resDir, fontName, dist);
    }
  }
}

Future<void> buildAnIconFont(
    String resDir, String fontName, String dist) async {
  String fontPath = '$resDir/$fontName.ttf';
  String cssPath = '$resDir/$fontName.css';
  File target = File(path.join(Directory.current.path, cssPath));
  if (!target.existsSync()) return;

  String str = await target.readAsString();

  List<String> names = [];
  List<String> unicodes = [];
  StringScanner _scanner = StringScanner(str);

  while (!_scanner.isDone) {
    if (_scanner.scan(RegExp(r'\.icon-(.*?):'))) {
      String word = _scanner.lastMatch[1];
      names.add(word);
    }

    if (_scanner.scan(RegExp(r'"\\(.*?)"'))) {
      String word = _scanner.lastMatch[1];
      unicodes.add(word);
    }

    if (!_scanner.isDone) {
      _scanner.position++;
    }
  }
  assert(names.length == unicodes.length);

  Map<String, String> iconMap = Map.fromIterables(names, unicodes);
  String code = getCode(iconMap, fontName: fontName);
  await save2File(code, filePath: dist, fontName: fontName);
  await handleYaml(family: fontName, asset: fontPath);
  if (deleteCss) await target.delete();
  // 删除样式文件
  print('创建 $fontName 完毕!');
}

Future<void> handleYaml({String family = 'TolyIcon',
    String asset = 'assets/iconfont/iconfont.ttf'}) async {
  File yamlFile = File(path.join(Directory.current.path, 'pubspec.yaml'));
  List<String> yamlLines = await yamlFile.readAsLines();
  RegExp fontsReg = RegExp(r'^  fonts\:');
  RegExp familyReg = RegExp(r'\- family:.*' + family);
  RegExp commentReg = RegExp(r'#.*');

  int fontLine = -1;
  int familyLine = -1;
  for (int i = 0; i < yamlLines.length; i++) {
    // 去除注释
    String pureLine = yamlLines[i].replaceAll(commentReg, '');
    if (fontsReg.hasMatch(pureLine)) {
      fontLine = i;
    }
    if (familyReg.hasMatch(pureLine)) {
      familyLine = i;
    }
  }

  String config = """
    - family: $family
      fonts:
        - asset: $asset""";

  if (fontLine == -1) {
    // fontLine 不存在,则添加 fonts: 节点和配置
    yamlLines.add('  fonts: ');
    yamlLines.add(config);
  } else {
    if (familyLine == -1) {
      // familyLine 不存在,则添加节点和配置
      yamlLines.insert(fontLine + 1, config);
    } else {
      // 否则说明该图标字体已配置,无须处理
      return;
    }
  }
  await yamlFile.writeAsString(yamlLines.join('\n'));
}

Future<void> save2File(String content,
    {String filePath: 'generate/icon', String fontName: 'TolyIcon'}) async {

  if(mergeClass){
    File target = File(
        path.join(Directory.current.path, 'lib', filePath, '$className.dart'));
    if (!target.existsSync()) {
      await target.create(recursive: true);
    }
    await target.writeAsString(content,mode: FileMode.append);
  }else{
    File target = File(
        path.join(Directory.current.path, 'lib', filePath, '$fontName.dart'));
    if (!target.existsSync()) {
      await target.create(recursive: true);
    }
    await target.writeAsString(content);
  }
}

String getCode(Map<String, String> iconMap, {String fontName: 'TolyIcon'}) {
  String content = '';
  iconMap.forEach((key, value) {
    content +=
        """static const IconData $key = IconData( 0x$value, fontFamily: "$fontName");\n""";
  });

  if (mergeClass) {
    return content;
  }
  String result = """import 'package:flutter/widgets.dart';
//Power By 张风捷特烈 --- Generated file. Do not edit.

class $fontName {
    $fontName._(); 
""";
  result += content;
  result += "}";
  return result;
}
复制代码

@张风捷特烈 2021.01.24 未允禁转 我的公众号:编程之王 联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328 ~ END ~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零、前言
    • 1. 前情简介
      • 2.本系列其他文章
      • 一、 pubspec.yaml 中配置自动生成
        • 1.需求分析
          • 2.分析 pubspec.yaml
            • 3.可配置参数
            • 二、 多个字体图标文件处理
              • 1.多图标字体分析
                • 2.代码处理
                  • 3.字体类的融合
                  • 三、icon_builder.dart 完整代码
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档