在React Native中优雅的使用iconfont

Posted on 2016-05-17 in React-Native by yucongchen

React Native大火大热,其中为了解决图标,易于修改,换颜色,高清等需求,iconfont的应用更是必不可少。

React Native中的iconfont

关于在React Native中使用iconfont,网上已有很多非常好的解决方案,用的最多的就是react-native-vector-icons , 这个库支持很多常用的iconfont,比如FontAwesome, Ionicons, MaterialIcons等等。

但是这个库依赖了不少iOS和Android的原生代码,这让一个前端开发脸上浮现了一个大大的懵逼。 而且自带的字体文件都偏大,做起精简来简直想哭,更别说加入自定义的iconfont了。

IconFont的使用原理

其实IconFont就是一些文字,通过在web上的使用,我们可以大概猜出使用方法:

  1. 指定字体集
  2. 把对应的16进制码当成文字写到文本中

在React Native中同样如此,我们可以通过react-native-vector-icons的源代码来验证我们的想法。

打开react-native-vector-icons/FontAweson.js文件(线上地址)可以看到一个大大的json对象

var createIconSet = require('./lib/create-icon-set');
var glyphMap = {
  "glass": 61440,
  "music": 61441,
  "search": 61442,
  "envelope-o": 61443,
  "heart": 61444,
  .
  . // 此处省略500+行
  .
};
var FontAwesome = createIconSet(glyphMap, 'FontAwesome', 'FontAwesome.ttf');
module.exports = FontAwesome;
module.exports.glyphMap = glyphMap;

看到这些亲切的数字了吗,61440,这不就是传说中的16进制的FXXX的十进制吗?

16进制有了,写到哪里呢,继续看createIconSet方法。

其中的Icon组件的render方法:

render: function() {
  var { name, size, color, style, ...props } = this.props;
  var glyph = glyphMap[name] || '?';
  if(typeof glyph === 'number') {
    glyph = String.fromCharCode(glyph);
  }
  size = size || DEFAULT_ICON_SIZE;
  var styleDefaults:Object = {
    fontSize: size,
    fontWeight: 'normal',
    fontStyle: 'normal',
    color,
  };
  props.style = [styleDefaults, style];
  props.ref = (component) => this._root = component;
  styleDefaults.fontFamily = fontReference;
  console.log(this.props.children)
  return (<Text {...props}>{glyph}{this.props.children}</Text>);
}

其中,最重要的四句话_(我本来写的是两句话,结果越看越多)_:

var glyph = glyphMap[name] || '?'; 把刚才的6xxxx找到;

glyph = String.fromCharCode(glyph); 转成Unicode编码字符串;

styleDefaults.fontFamily = fontReference; 指定字符集;

return (<Text {...props}>{glyph}{this.props.children}</Text>); 把Unicode字符写到Text组件中。

基本和我们的猜想一样,哇哈哈哈。

Font的基本知识

由上可知,我们主要需要这个Icon所对应的Unicode码,那这个Unicode码又是神马呢?

实际上,一个字体通常由数个表(table)构成,字体的信息存储在表中。一个最基本的字体文件一定会包含以下表:

  • cmap: Char­ac­ter to glyph map­ping
  • head: Font header
  • hhea: Hor­i­zon­tal header
  • hmtx: Hor­i­zon­tal met­rics
  • maxp: Max­i­mum pro­file
  • name: Nam­ing table
  • OS/​2: OS/​2 and Win­dows spe­cific met­rics
  • post: Post­Script in­for­ma­tion

而使用TrueType曲线绘制的字体则会包含如下表:

  • cvt: Con­trol Value Table
  • fpgm: Font pro­gram
  • glyf: Glyph data
  • loca: In­dex to lo­ca­tion
  • prep: CVT Pro­gram
  • gasp: Grid​-​fit­ting/​Scan​-​con­ver­sion (op­tional table)

上面列了很多,最重要的其实是第一个表看这高大上的说明Char­ac­ter to glyph map­ping

如果把字体文件转成类xml格式,这个表类似:

<cmap> 
    <cmap_format_4 platformID="3" platEncID="1" language="0">
          <map code="0xf600" name="uniF600"/>
          <map code="0xf601" name="uniF601"/>
          <map code="0xf602" name="uniF602"/>
          <map code="0xf603" name="uniF603"/>
    </cmap_format_4>
</cmap>

这里的0xf600不就是我们想要的吗,而后面的name就类似与每个字符的命名。如下表:

在React Native中优雅的使用iconfont

这里最好给每个icon定一个易于理解的名字,可以使用http://font.baidu.com/editor

使用自定义的IconFont

有了上面的摸索,要支持自己的IconFont并不难。只需要把字符对应表给整出来就可以了,代码如下:

var map = {"arrow":"62976","checked":"62977","checked-s":"62978","tag-svip":"62995"};
;module.exports = (name)=>String.fromCharCode(map[name]);

使用的时候:

import icon from "./fontConf";
export default class  IconExample extends Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={{fontFamily: 'FontIconQui',fontSize:30}}>
                    arrow-icon:{icon('arrow')}
                </Text>
                <Text style={{fontFamily: 'FontIconQui',fontSize:30, color:"#ff4444"}}>
                    vip-icon:{icon('tag-svip')}
                </Text>
                <Text style={{fontFamily: 'FontIconQui',fontSize:30, color:"#ff4444"}}>
                    tag-svip:{icon('tag-svip')}
                </Text>
            </View>
        )
    }
}

另外,在工程中,需要引入字体文件:

  • Android: 把字体文件拷贝到[project root]/android/app/src/main/assets/fonts/
  • iOS: 把字体文件拖到对应的Xcode工程里面,勾选Add to targetsCreate groups,修改Info.plist文件,添加属性Fonts provided by application,在这个属性下添加相应字体文件名的item,如下图:

在React Native中优雅的使用iconfont

iOS上添加字体文件具体的流程可以参考https://github.com/oblador/react-native-vector-icons#option-manually

自动提取字符对应表

你以为做完上面的工作就完了吗,如果你只是对着一个10k的ttf文件,那么左手右手一个慢动作,靠着五姑娘的勤劳也可以很快完成,但是如果面对的是个有700个图标的FontAwesome怎么办?

大神说过: 所有超过五分钟的事情都应该自动化。
大神还说过: 人生苦短,我用python。

所以,我用python撸了个脚本来自动生成字符对应表的代码。

代码很简单,就是读之前说的那个cmap表。

tmpl = """
var map = {%s};
;module.exports = (name)=>String.fromCharCode(map[name]);
"""    
def main(fontFile, output):
    try:
        font = TTFont(fontFile)
        glyphMap = font["cmap"].getcmap(3,1).cmap
        tmp = ""
        for k in glyphMap:
            tmp += '"%s":"%s",' % (glyphMap[k],k)

        f=file(output,"w+")
        f.write(tmpl % tmp)
        f.close()   
    except Exception, ex:
        print ex

用法: $ python iconfont-mapper.py fontawesome.ttf fontawesomeConf.js

依赖fontTools这个库,完整代码在https://github.com/bob-chen/react-native-iconfont-mapper

碎碎念

记录一些所思所想,写写科技与人文,写写生活状态,写写读书心得,主要是扯淡和感悟。 欢迎关注,交流。

微信公众号:程序员的诗和远方

公众号ID : MonkeyCoder-Life

程序员的诗和远方