React Native大火大热,其中为了解决图标,易于修改,换颜色,高清等需求,iconfont的应用更是必不可少。
React Native中的iconfont
关于在React Native中使用iconfont,网上已有很多非常好的解决方案,用的最多的就是react-native-vector-icons , 这个库支持很多常用的iconfont,比如FontAwesome, Ionicons, MaterialIcons等等。
但是这个库依赖了不少iOS和Android的原生代码,这让一个前端开发脸上浮现了一个大大的懵逼。 而且自带的字体文件都偏大,做起精简来简直想哭,更别说加入自定义的iconfont了。
IconFont的使用原理
其实IconFont就是一些文字,通过在web上的使用,我们可以大概猜出使用方法:
- 指定字体集
- 把对应的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 ({glyph}{this.props.children});
}
其中,最重要的四句话_(我本来写的是两句话,结果越看越多)_:
var glyph = glyphMap[name] || '?'; 把刚才的6xxxx找到;
glyph = String.fromCharCode(glyph); 转成Unicode编码字符串;
styleDefaults.fontFamily = fontReference; 指定字符集;
return ({glyph}{this.props.children}); 把Unicode字符写到Text组件中。
基本和我们的猜想一样,哇哈哈哈。
Font的基本知识
由上可知,我们主要需要这个Icon所对应的Unicode码,那这个Unicode码又是神马呢?
实际上,一个字体通常由数个表(table)构成,字体的信息存储在表中。一个最基本的字体文件一定会包含以下表:
- cmap: Character to glyph mapping
- head: Font header
- hhea: Horizontal header
- hmtx: Horizontal metrics
- maxp: Maximum profile
- name: Naming table
- OS/2: OS/2 and Windows specific metrics
- post: PostScript information
而使用TrueType曲线绘制的字体则会包含如下表:
- cvt: Control Value Table
- fpgm: Font program
- glyf: Glyph data
- loca: Index to location
- prep: CVT Program
- gasp: Grid-fitting/Scan-conversion (optional table)
上面列了很多,最重要的其实是第一个表看这高大上的说明Character to glyph mapping。
如果把字体文件转成类xml格式,这个表类似:
这里的0xf600不就是我们想要的吗,而后面的name就类似与每个字符的命名。如下表:
这里最好给每个icon定一个易于理解的名字,可以使用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 (
arrow-icon:{icon('arrow')}
vip-icon:{icon('tag-svip')}
tag-svip:{icon('tag-svip')}
)
}
}
另外,在工程中,需要引入字体文件:
- Android: 把字体文件拷贝到
[project root]/android/app/src/main/assets/fonts/ - iOS: 把字体文件拖到对应的Xcode工程里面,勾选
Add to targets和Create groups,修改Info.plist文件,添加属性Fonts provided by application,在这个属性下添加相应字体文件名的item,如下图:
iOS上添加字体文件具体的流程可以参考github.com/oblador/rea…。
自动提取字符对应表
你以为做完上面的工作就完了吗,如果你只是对着一个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)[https://github.com/bob-chen/react-native-iconfont-mapper]