表情包字體庫

1 概述

最近項(xiàng)目中要添加表情包聊天的功能(文本和表情包要混合在一起)红氯,最直接的解決方案應(yīng)該就是圖文混排框咙,對(duì)于這個(gè)方案網(wǎng)上有很多的實(shí)現(xiàn),圖文混排實(shí)現(xiàn)起來比較麻煩痢甘,而且和服務(wù)端交互的時(shí)候還要將圖片和與之對(duì)應(yīng)的字符串之間進(jìn)行變換喇嘱,對(duì)于有追求的我來說,這個(gè)方案我是無法接受的塞栅,因此直接被否定者铜;表情包是用來聊天的,如果每一個(gè)表情包可以看著是一個(gè)英文字符放椰,那樣實(shí)現(xiàn)起來豈不是完美作烟,既不用考慮與服務(wù)端的交互,也不用像圖文混排那樣寫自定義的圖文混排控件(直接使用TextView和EditText就行)砾医,那么用表情包圖片生成與之對(duì)應(yīng)的字體庫就是最好的解決方案拿撩,第一時(shí)間想到的就是iconfont,如下圖所示:


顧名思義就是圖形字體如蚜,但是遺憾的對(duì)于Android開發(fā)只有黑白的压恒,沒有彩色的,因此被否定了错邦,然后就開始通過Google搜索解決方案探赫,最終找到了Google提供的解決方案color-emoji,也就是最終采取的解決方案撬呢。

color-emoji用到的字體庫是TTF(TrueType fonts)字體庫伦吠,TrueType是Apple和Microsoft在20世紀(jì)80年代后期開發(fā)的輪廓字體標(biāo)準(zhǔn),作為在PostScript中使用的AdobeType 1 fonts的競(jìng)爭(zhēng)對(duì)手。 它已成為macOS和Microsoft Windows操作系統(tǒng)上最常用的字體格式讨勤。TrueType的主要優(yōu)勢(shì)在于它為字體開發(fā)人員提供了對(duì)字體顯示精確程度的高度控制(小到到特定像素箭跳、各種字體大小)。 由于目前使用的渲染技術(shù)差異很大潭千,因此不再需要TrueType字體中的像素級(jí)控制谱姓。TrueType字體中的字符(或字形)的輪廓由直線段和二次貝塞爾曲線組成。

Apple已經(jīng)實(shí)現(xiàn)了一個(gè)專有擴(kuò)展刨晴,為其emoji字體Apple Color Emoji提供彩色的.ttf文件屉来;在iOS 5之前,使用SoftBank編碼在Apple設(shè)備上編碼emoji狈癞。 從iOS 5開始茄靠,emoji使用Unicode標(biāo)準(zhǔn)進(jìn)行編碼。emoji字形存儲(chǔ)為PNG圖像蝶桶,后來在OpenType1.8版本中進(jìn)行了標(biāo)準(zhǔn)化慨绳。

在Apple Color Emoji推出多年之后,在2013年真竖,Google終于也推出了自己的開源Color Font標(biāo)準(zhǔn):Open Standard Font Fun for Everyone 脐雪,Google同樣實(shí)現(xiàn)了OpenType的標(biāo)準(zhǔn),并且提供了一個(gè)開源的實(shí)現(xiàn):color-emoji恢共。

說點(diǎn)題外話战秋,表情包使我們的生活越來越生動(dòng),展示一下工作中的表情包:


我怎么這么好看讨韭,這么好看怎么辦

憋說話脂信,吻我

嘿嘿嘿

微微一笑很傾城

看看我的牙白嗎

以上圖片我會(huì)用在下面的例子中,讓大家從歡笑中體驗(yàn)字體庫的魅力透硝。

2 預(yù)備知識(shí)

2.1 字符編碼

2.1.1 字符

所有國(guó)家的文字狰闪、符號(hào)等都是由字符組成的。

2.1.2 字符集

字符集是字符的集合濒生,字符集有很多尝哆,常見字符集有:ASCII字符集ISOxxx字符集甜攀、GBxxx字符集Unicode字符集等琐馆,字符集只規(guī)定了字符的編號(hào)规阀,卻沒有規(guī)定這個(gè)編號(hào)應(yīng)該如何存儲(chǔ)(由字符編碼規(guī)定)。

2.1.3 字符編碼

計(jì)算機(jī)能夠識(shí)別和存儲(chǔ)字符集中的字符瘦麸,就需要對(duì)字符集中的字符進(jìn)行字符編碼谁撼,字符編碼就是規(guī)定每個(gè)字符是用一個(gè)字節(jié)還是多個(gè)字節(jié)存儲(chǔ),這個(gè)規(guī)定就叫做字符編碼;各個(gè)國(guó)家和地區(qū)在制定編碼標(biāo)準(zhǔn)的時(shí)候厉碟,字符集合和字符編碼一般都是同時(shí)制定的喊巍,因此平時(shí)所說的ASCIIGB2312箍鼓、GBK崭参、UTF-8UTF-16款咖、UTF-32等即是字符集合同時(shí)也是字符編碼何暮。

2.1.4 Unicode字符集

由于編碼的不同,同一個(gè)二進(jìn)制數(shù)字可以被解釋成不同的符號(hào)铐殃,因此打開一個(gè)文本文件就必須知道它的編碼海洼,否則用錯(cuò)誤的編碼方式解讀就會(huì)出現(xiàn)亂碼;如果有一個(gè)將世界上所有的符號(hào)都納入其中字符集并且每一個(gè)符號(hào)都給予一個(gè)獨(dú)一無二的編碼富腊,那么亂碼問題就會(huì)消失坏逢,這就是Unicode;Unicode 是一個(gè)很大的集合赘被,現(xiàn)在的規(guī)氖钦可以容納100多萬個(gè)符號(hào),每個(gè)符號(hào)的編碼都不一樣帘腹,具體的符號(hào)對(duì)應(yīng)表贰盗,可以查詢unicode編碼表,或者專門的漢字對(duì)應(yīng)表阳欲。

2.1.5 ASCII 字符編碼

ASCII 字符編碼一共包含128個(gè)字符的編碼(包括32個(gè)不能打印出來的控制符號(hào))舵盈,比如換行LF是10(二進(jìn)制00001010),大寫的字母C是67(二進(jìn)制0100 0011)球化,只用了一個(gè)字節(jié)的后7位秽晚,最前面的一位統(tǒng)一規(guī)定為0。

2.1.6 UTF-8 字符編碼

Unicode字符集有多種字符編碼筒愚,如UTF-8赴蝇、UTF-16UTF-32巢掺,其中UTF-8是最廣泛使用的句伶,UTF-8最大的特點(diǎn)就是使用一種變長(zhǎng)的編碼方式,它可以使用1~4個(gè)字節(jié)表示一個(gè)字符陆淀,對(duì)于0x00-0x7F之間的字符考余,UTF-8字符編碼ASCII字符編碼完全相同(即使用一個(gè)字節(jié)進(jìn)行字符編碼),因此英語字母的UTF-8字符編碼ASCII字符編碼是相同的轧苫,對(duì)于使用n(n > 1)字節(jié)的進(jìn)行編碼的字符楚堤,第一個(gè)字節(jié)的前n位都設(shè)為1,第n + 1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10身冬,剩下的沒有提及的二進(jìn)制位衅胀,全部為這個(gè)符號(hào)的 Unicode 碼,編碼規(guī)則如下表所示(x表示可用編碼的位):

Unicode字符編碼范圍(十六進(jìn)制) |       UTF-8編碼方式 (二進(jìn)制)
--------------------------+---------------------------------------------
0000 0000-0000 007F       |       0xxxxxxx
0000 0080-0000 07FF       |       110xxxxx 10xxxxxx
0000 0800-0000 FFFF       |       1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF       |       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟據(jù)上表酥筝,解讀UTF-8字符編碼非常簡(jiǎn)單滚躯,如果一個(gè)字節(jié)的第一位是0,則這個(gè)字節(jié)單獨(dú)就是一個(gè)字符樱哼;如果第一位是1哀九,則連續(xù)有多少個(gè)1,就表示當(dāng)前字符占用多少個(gè)字節(jié)搅幅;比如字在Unicode字符集中的編號(hào)為7F3A(111111100111010)阅束,根據(jù)上表可以發(fā)現(xiàn)字處在第三行的范圍內(nèi)(0000 0800 - 0000 FFFF),因此字的UTF-8字符編碼需要三個(gè)字節(jié)茄唐,即格式是1110xxxx 10xxxxxx 10xxxxxx息裸,然后從字的最后一個(gè)二進(jìn)制位開始依次從后向前填充格式中的x,多出的位補(bǔ)0沪编,這樣就得到了字的UTF-8編碼是11100111 10111100 10111010呼盆,轉(zhuǎn)換成十六進(jìn)制就是E7BCBA

Android項(xiàng)目中字符用的是UTF-8字符編碼蚁廓,對(duì)應(yīng)Unicode字符集访圃,字對(duì)應(yīng)的Unicode編號(hào)就被占用了,對(duì)于表情包字體庫就要占用Unicode字符集中沒有被占用的編號(hào)相嵌,即就要知道那些范圍的字符編號(hào)是可以自定義用的腿时,下面是維基百科上給出的可以自定義編號(hào)的范圍:


可惜的是 color-emoji只支持第一個(gè)范圍中的編號(hào),但是對(duì)于表情包來說已經(jīng)足夠了饭宾。

2.2 TextView中的setFakeBoldTextsetTextSkewX方法

setFakeBoldText : 設(shè)置字體為偽粗體批糟,之所以叫偽粗體fake bold,因?yàn)樗⒉皇鞘褂酶?code>weight的字體讓文字變粗看铆,而是通過程序在運(yùn)行時(shí)把文字給描粗了,效果與與下面2.4 中的bold一樣徽鼎。
setTextSkewX:設(shè)置文本繪制的水平傾斜因子,默認(rèn)值為0弹惦,正數(shù)代表向左傾斜否淤,負(fù)數(shù)代表向右傾斜,為了近似斜文本棠隐,請(qǐng)使用-0.25左右的值石抡,下面2.4 中的italic也是調(diào)用的該方法設(shè)置水平傾斜因子為-0.25

2.3 Font Weight

用來指定字體的權(quán)重宵荒,即字體筆觸的粗細(xì),可取的值及其意義如下:
100900
這些值組成一個(gè)有序序列,下面是與這些值大致對(duì)應(yīng)的權(quán)重名稱:

100 - Thin
200 - Extra Light (Ultra Light)
300 - Light
400 - Normal 默認(rèn)值
500 - Medium
600 - Semi Bold (Demi Bold)
700 - Bold
800 - Extra Bold (Ultra Bold)
900 - Black (Heavy)
normal
Same as ‘400’.
bold
Same as ‘700’.

通常指定的字體庫只有上面幾種權(quán)重值报咳,當(dāng)指定權(quán)重存在時(shí)侠讯,則使用該權(quán)重,否者使用下面的規(guī)則來匹配權(quán)重:
1> 如果所需的權(quán)重小于400暑刃,則首先降序檢查小于所需權(quán)重的各個(gè)權(quán)重厢漩,如仍然沒有,則升序檢查大于所需字重的各權(quán)重统诺,直到找到匹配的權(quán)重环戈。
2> 如果所需的權(quán)重大于500工三,則首先升序檢查大于所需權(quán)重的各權(quán)重,之后降序檢查小于所需權(quán)重的各權(quán)重炸宵,直到找到匹配的權(quán)重。
3> 如果所需的權(quán)重是400谷扣,那么會(huì)優(yōu)先匹配500對(duì)應(yīng)的權(quán)重土全,如仍沒有,那么執(zhí)行第一條所需權(quán)重小于400的規(guī)則会涎。
4> 如果所需的權(quán)重是500裹匙,則優(yōu)先匹配400對(duì)應(yīng)的權(quán)重,如仍沒有末秃,那么執(zhí)行第一條所需權(quán)重小于400的規(guī)則
下圖說明了權(quán)重的匹配規(guī)則概页,灰色表示權(quán)重不存在,需要匹配權(quán)重:


具有400,700和900權(quán)重值的字體的權(quán)重映射

具有300练慕、600重量值的字體的權(quán)重映射

對(duì)于TTF(TrueType fonts)字體庫引入了從100到900的比例惰匙,其中400是Normal,因此對(duì)于3中生成FruityGirl.ttf字體庫擁有上面所有的權(quán)重值贺待,在Android的layout文件中使用android:fontFamily屬性設(shè)置字體時(shí)徽曲,默認(rèn)的權(quán)重值Normal

2.4 Font Style

用來指定字體的樣式,可取的值及其意義如下:
normal:普通麸塞,默認(rèn)值
italic:斜體
bold:粗體 與Font Weight中700相對(duì)應(yīng)
在Android的layout文件中使用android:textStyle屬性設(shè)置字體的樣式

2.5 Android設(shè)置字體的方法

2.5.1 系統(tǒng)字體的初始化

首先看一下我測(cè)試機(jī)上字體配置文件/system/etc/font.xml的部分內(nèi)容:

<?xml version="1.0" encoding="utf-8"?>
<!--
    All fonts without names are added to the default list. Fonts are chosen
    based on a match: full BCP-47 language tag including script, then just
    language, and finally order (the first font containing the glyph).

    Order of appearance is also the tiebreaker for weight matching. This is
    the reason why the 900 weights of Roboto precede the 700 weights - we
    prefer the former when an 800 weight is requested. Since bold spans
    effectively add 300 to the weight, this ensures that 900 is the bold
    paired with the 500 weight, ensuring adequate contrast.
-->
<familyset version="22">
    <!-- first font is default -->
    <family name="sans-serif">
        <font weight="100" style="normal">Roboto-Thin.ttf</font>
        <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
        <font weight="300" style="normal">Roboto-Light.ttf</font>
        <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
        <font weight="400" style="normal">Roboto-Regular.ttf</font>
        <font weight="400" style="italic">Roboto-Italic.ttf</font>
        <font weight="500" style="normal">Roboto-Medium.ttf</font>
        <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
        <font weight="900" style="normal">Roboto-Black.ttf</font>
        <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
        <font weight="700" style="normal">Roboto-Bold.ttf</font>
        <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
    </family>

    <!-- Note that aliases must come after the fonts they reference. -->
    <alias name="sans-serif-thin" to="sans-serif" weight="100" />
    <alias name="sans-serif-light" to="sans-serif" weight="300" />
    <alias name="sans-serif-medium" to="sans-serif" weight="500" />
    <alias name="sans-serif-black" to="sans-serif" weight="900" />
    <alias name="arial" to="sans-serif" />
    <alias name="helvetica" to="sans-serif" />
    <alias name="tahoma" to="sans-serif" />
    <alias name="verdana" to="sans-serif" />

    <family name="sans-serif-condensed">
        <font weight="300" style="normal">RobotoCondensed-Light.ttf</font>
        <font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
        <font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
        <font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
        <font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
        <font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
    </family>
    <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />

    <family name="serif">
        <font weight="400" style="normal">NotoSerif-Regular.ttf</font>
        <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
        <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
        <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
    </family>
    ...
    <!-- 簡(jiǎn)體中文字體 -->
    <family lang="zh-Hans">
        <font weight="400" style="normal" index="2">SECCJK-Regular.ttc</font>
    </family>
    <!-- 繁體中文字體 -->
    <family lang="zh-Hant">
        <font weight="400" style="normal" index="3">SECCJK-Regular.ttc</font>
    </family>

/system/etc/font.xml文件的第一個(gè)family節(jié)點(diǎn)中的weight="400" style="normal"對(duì)應(yīng)的字體庫會(huì)作為默認(rèn)字體庫秃臣,后面會(huì)在源碼上說明這一點(diǎn),/system/etc/font.xml文件配置的字體就是系統(tǒng)提供的所有字體哪工,解析上面的配置文件主要是在android.graphics.Typeface完成的奥此,接下里首先通過下面的時(shí)序圖從全局上看一下解析的流程:

注意 本文是根據(jù)API26版本源碼分析的

首先看一下第二步的源碼:

private static File getSystemFontConfigLocation() {
    return new File("/system/etc/");
}

static final String FONTS_CONFIG = "fonts.xml";

/*
 * 該方法只會(huì)被調(diào)用一次,即上面的靜態(tài)代碼塊中
 */
private static void init() {
    // Load font config and initialize Minikin state
    // 創(chuàng)建與字體配置文件(/system/etc/font.xml)相對(duì)應(yīng)的File對(duì)象
    File systemFontConfigLocation = getSystemFontConfigLocation();
    File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
    try {
        FileInputStream fontsIn = new FileInputStream(configFilename);
        FontConfig fontConfig = FontListParser.parse(fontsIn);

        Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();

        List<FontFamily> familyList = new ArrayList<FontFamily>();
        // Note that the default typeface is always present in the fallback list;
        // this is an enhancement from pre-Minikin behavior.
        // 獲取font.xml中的默認(rèn)字體family并且保存到familyList中雁比,即上圖中的第3步
        for (int i = 0; i < fontConfig.getFamilies().length; i++) {
            FontConfig.Family f = fontConfig.getFamilies()[i];
            if (i == 0 || f.getName() == null) {
                // 解析/system/etc/font.xml文件中的family節(jié)點(diǎn)然后創(chuàng)建FontFamily對(duì)象
                FontFamily family = makeFamilyFromParsed(f, bufferForPath);
                if (family != null) {
                    familyList.add(family);
                }
            }
        }
        sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
        // 使用系統(tǒng)默認(rèn)字體對(duì)應(yīng)的FontFamily對(duì)象創(chuàng)建Typeface對(duì)象稚虎,即上圖中的第4步
        setDefault(Typeface.createFromFamilies(sFallbackFonts));

        // 解析/system/etc/font.xml`文件配置的系統(tǒng)字體并且保存到sSystemFontMap中,即上圖的第5步
        Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
        for (int i = 0; i < fontConfig.getFamilies().length; i++) {
            Typeface typeface;
            FontConfig.Family f = fontConfig.getFamilies()[i];
            if (f.getName() != null) {
                if (i == 0) {
                    // The first entry is the default typeface; no sense in
                    // duplicating the corresponding FontFamily.
                    typeface = sDefaultTypeface;
                } else {
                    // 解析/system/etc/font.xml文件中的family節(jié)點(diǎn)然后創(chuàng)建FontFamily對(duì)象
                    FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
                    if (fontFamily == null) {
                        continue;
                    }
                    FontFamily[] families = { fontFamily };
                    typeface = Typeface.createFromFamiliesWithDefault(families,
                            RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
                }
                systemFonts.put(f.getName(), typeface);
            }
        }
        for (FontConfig.Alias alias : fontConfig.getAliases()) {
            Typeface base = systemFonts.get(alias.getToName());
            Typeface newFace = base;
            int weight = alias.getWeight();
            if (weight != 400) {
                newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
            }
            systemFonts.put(alias.getName(), newFace);
        }
        sSystemFontMap = systemFonts;

    } catch (RuntimeException e) {
        Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
        // TODO: normal in non-Minikin case, remove or make error when Minikin-only
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Error opening " + configFilename, e);
    } catch (IOException e) {
        Log.e(TAG, "Error reading " + configFilename, e);
    } catch (XmlPullParserException e) {
        Log.e(TAG, "XML parse exception for " + configFilename, e);
    }
}

上面的代碼主要做了上圖中3偎捎、4蠢终、5序攘、6步,第3步和5步很簡(jiǎn)單寻拂,就是解析/system/etc/font.xml文件程奠,有興趣的同學(xué)可以自己看一下源碼,下面詳細(xì)講解一下第4祭钉、6步:

public static final int RESOLVE_BY_FONT_TABLE = -1;

// 第4步調(diào)用的方法
private static void setDefault(Typeface t) {
    sDefaultTypeface = t;
    // 將style為NORMAL瞄沙、weight為400的默認(rèn)系統(tǒng)字體對(duì)應(yīng)的Typeface對(duì)象保存到native層中
    nativeSetDefault(t.native_instance);
}

private Typeface(long ni) {
    if (ni == 0) {
        throw new RuntimeException("native typeface cannot be made");
    }

    native_instance = ni;
    // 獲取下面native代碼中createFromFamilies方法中fSkiaStyle的值保存到mStyle中
    mStyle = nativeGetStyle(ni);
    mWeight = nativeGetWeight(ni);
}

private static Typeface createFromFamilies(FontFamily[] families) {
    long[] ptrArray = new long[families.length];
    for (int i = 0; i < families.length; i++) {
        ptrArray[i] = families[i].mNativePtr;
    }
    // 創(chuàng)建style為NORMAL、weight為400的默認(rèn)系統(tǒng)字體對(duì)應(yīng)的Typeface對(duì)象
    return new Typeface(nativeCreateFromArray(
            ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
}

/**
 * 第6步調(diào)用的方法
 * Create a new typeface from an array of font families, including
 * also the font families in the fallback list.
 * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that
 *               case, the table information in the first family's font is used. If the first
 *               family has multiple fonts, the closest to the regular weight and upright font
 *               is used.
 * @param italic the italic information for this family. {@link RESOLVE_BY_FONT_TABLE} can be
 *               used. In that case, the table information in the first family's font is used.
 *               If the first family has multiple fonts, the closest to the regular weight and
 *               upright font is used.
 * @param families array of font families
 */
private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
            int weight, int italic) {
    long[] ptrArray = new long[families.length + sFallbackFonts.length];
    for (int i = 0; i < families.length; i++) {
        ptrArray[i] = families[i].mNativePtr;
    }
    for (int i = 0; i < sFallbackFonts.length; i++) {
        ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
    }
    return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
}

上面對(duì)于RESOLVE_BY_FONT_TABLE常量的解釋說明了如下消息:
nativeCreateFromArray23個(gè)參數(shù)只要有一個(gè)參數(shù)是RESOLVE_BY_FONT_TABLE慌核,則參數(shù)ptrArray數(shù)組第一個(gè)元素會(huì)被使用距境,如果第一個(gè)元素下有多個(gè)font節(jié)點(diǎn),則weight為Normal(即regular)且style為upright也就是Normal的字體庫會(huì)被使用垮卓,接下來看下native層的方法nativeCreateFromArray的源碼:

static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray,
        int weight, int italic) {
    ScopedLongArrayRO families(env, familyArray);
    std::vector<std::shared_ptr<minikin::FontFamily>> familyVec;
    familyVec.reserve(families.size());
    for (size_t i = 0; i < families.size(); i++) {
        FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(families[i]);
        familyVec.emplace_back(family->family);
    }
    return reinterpret_cast<jlong>(
            Typeface::createFromFamilies(std::move(familyVec), weight, italic));
}

// FontStyle的默認(rèn)構(gòu)造函數(shù)垫桂,其中weight值范圍是1到9,即對(duì)應(yīng)于2.2中的100到900扒接,這樣做的目的是用4bit保存weight值
FontStyle() : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}

Typeface* Typeface::createFromFamilies(
        std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
        int weight, int italic) {
    Typeface* result = new Typeface;
    result->fFontCollection.reset(new minikin::FontCollection(families));

    // 這個(gè)判斷也就說明了上面RESOLVE_BY_FONT_TABLE注釋
    if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
        int weightFromFont;
        bool italicFromFont;

        // 上面的默認(rèn)構(gòu)造方法會(huì)被調(diào)用
        const minikin::FontStyle defaultStyle;
        // families[0]獲取的就是第一個(gè)family節(jié)點(diǎn)伪货,然后通過getClosestMatch方法找到最接近defaultStyle的字體
        const minikin::MinikinFont* mf =
                families.empty() ?  nullptr : families[0]->getClosestMatch(defaultStyle).font;
        if (mf != nullptr) {
            // 對(duì)于我的測(cè)試機(jī),一定會(huì)走到這里钾怔,最終weightFromFont為400碱呼,italicFromFont為false
            SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
            const SkFontStyle& style = skTypeface->fontStyle();
            weightFromFont = style.weight();
            italicFromFont = style.slant() != SkFontStyle::kUpright_Slant;
        } else {
            // We can't obtain any information from fonts. Just use default values.
            weightFromFont = SkFontStyle::kNormal_Weight;
            italicFromFont = false;
        }

        if (weight == RESOLVE_BY_FONT_TABLE) {
            weight = weightFromFont;
        }
        if (italic == RESOLVE_BY_FONT_TABLE) {
            italic = italicFromFont? 1 : 0;
        }
    }

    // Sanitize the invalid value passed from public API.
    if (weight < 0) {
        weight = SkFontStyle::kNormal_Weight;
    }

    result->fBaseWeight = weight;
    // 對(duì)于我的測(cè)試機(jī),weight為400宗侦,italic為false愚臀,因此fSkiaStyle為SkTypeface::kNormal
    result->fSkiaStyle = computeSkiaStyle(weight, italic);
    result->fStyle = computeMinikinStyle(weight, italic);
    return result;
}

static SkTypeface::Style computeSkiaStyle(int weight, bool italic) {
    // This bold detection comes from SkTypeface.h
    if (weight >= SkFontStyle::kSemiBold_Weight) {
        return italic ? SkTypeface::kBoldItalic : SkTypeface::kBold;
    } else {
        return italic ? SkTypeface::kItalic : SkTypeface::kNormal;
    }
}

enum Weight {
    kInvisible_Weight   =    0,
    kThin_Weight        =  100,
    kExtraLight_Weight  =  200,
    kLight_Weight       =  300,
    kNormal_Weight      =  400,
    kMedium_Weight      =  500,
    kSemiBold_Weight    =  600,
    kBold_Weight        =  700,
    kExtraBold_Weight   =  800,
    kBlack_Weight       =  900,
    kExtraBlack_Weight  = 1000,
};

enum Style {
    kNormal = 0,
    kBold   = 0x01,
    kItalic = 0x02,

    // helpers
    kBoldItalic = 0x03
};

上面講解完了/system/etc/font.xml文件的解析過程,接下來的7到13步都是用來創(chuàng)建系統(tǒng)字體對(duì)應(yīng)的Typeface對(duì)象:

// Android系統(tǒng)目前只提供了字體的四種Style,可以通過android:textStyle屬性設(shè)置
public static final int NORMAL = 0;
public static final int BOLD = 1;
public static final int ITALIC = 2;
public static final int BOLD_ITALIC = 3;

static {
    // 初始化系統(tǒng)字體
    init();
    // Set up defaults and typefaces exposed in public API
    // 對(duì)應(yīng)上圖中的第7步
    DEFAULT         = create((String) null, 0);
    // 對(duì)應(yīng)上圖中的第8步
    DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
    // 對(duì)應(yīng)上圖中的第9步
    SANS_SERIF      = create("sans-serif", 0);
    // 對(duì)應(yīng)上圖中的第10步
    SERIF           = create("serif", 0);
    // 對(duì)應(yīng)上圖中的第11步
    MONOSPACE       = create("monospace", 0);

    // sDefaults保存系統(tǒng)默認(rèn)字體的四種style(NORMAL矾利、BOLD姑裂、ITALIC、BOLD_ITALIC)對(duì)應(yīng)的Typeface
    sDefaults = new Typeface[] {
        DEFAULT,
        DEFAULT_BOLD,
        // 對(duì)應(yīng)上圖中的第12步
        create((String) null, Typeface.ITALIC),
        // 對(duì)應(yīng)上圖中的第13步
        create((String) null, Typeface.BOLD_ITALIC),
    };
}

/**
 * 根據(jù)字體family名稱和字體style創(chuàng)建Typeface對(duì)象男旗,如果字體family名稱為null則返回系統(tǒng)默認(rèn)字體對(duì)應(yīng)的Typeface對(duì)象
 */
public static Typeface create(String familyName, int style) {
    // sSystemFontMap保存的是上面init方法初始化的系統(tǒng)字體
    if (sSystemFontMap != null) {
        return create(sSystemFontMap.get(familyName), style);
    }
    return null;
}

public static Typeface create(Typeface family, int style) {
    if (style < 0 || style > 3) {
        style = 0;
    }
    long ni = 0;
    if (family != null) {
        // Return early if we're asked for the same face/style
        // 由第4步中mStyle的值可知上圖中第9舶斧、10、11步下面的判斷會(huì)成立察皇,直接返回
        if (family.mStyle == style) {
            return family;
        }

        ni = family.native_instance;
    }

    Typeface typeface;
    SparseArray<Typeface> styles = sTypefaceCache.get(ni);

    if (styles != null) {
        typeface = styles.get(style);
        if (typeface != null) {
            return typeface;
        }
    }
    
    // 上圖中的第7茴厉、8、12什荣、13會(huì)走到這里
    typeface = new Typeface(nativeCreateFromTypeface(ni, style));
    if (styles == null) {
        styles = new SparseArray<Typeface>(4);
        sTypefaceCache.put(ni, styles);
    }
    styles.put(style, typeface);

    return typeface;
}

接下來就是看一下nativeCreateFromTypeface方法:

static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
    Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
    Typeface* face = Typeface::createRelative(family, (SkTypeface::Style)style);
    // TODO: the following logic shouldn't be necessary, the above should always succeed.
    // Try to find the closest matching font, using the standard heuristic
    if (NULL == face) {
        face = Typeface::createRelative(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
    }
    for (int i = 0; NULL == face && i < 4; i++) {
        face = Typeface::createRelative(family, (SkTypeface::Style)i);
    }
    return reinterpret_cast<jlong>(face);
}

Typeface* Typeface::createRelative(Typeface* src, SkTypeface::Style style) {
    // 對(duì)于第7矾缓、8步得到的是默認(rèn)字體對(duì)象,對(duì)于12稻爬、13得到的是其對(duì)應(yīng)的系統(tǒng)字體對(duì)象
    Typeface* resolvedFace = Typeface::resolveDefault(src);
    Typeface* result = new Typeface;
    if (result != nullptr) {
        result->fFontCollection = resolvedFace->fFontCollection;
        result->fBaseWeight = resolvedFace->fBaseWeight;
        result->fSkiaStyle = style;
        result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
    }
    return result;
}

Typeface* Typeface::resolveDefault(Typeface* src) {
    // gDefaultTypeface 就是在第4步中設(shè)置的默認(rèn)字體對(duì)象
    LOG_ALWAYS_FATAL_IF(gDefaultTypeface == nullptr);
    return src == nullptr ? gDefaultTypeface : src;
}

到這里系統(tǒng)字體的初始化已經(jīng)分析完了嗜闻,得出如下結(jié)論:
系統(tǒng)根據(jù)字體配置文件/system/etc/font.xml進(jìn)行系統(tǒng)字體的初始化,該配置文件的第一個(gè)family節(jié)點(diǎn)會(huì)作為默認(rèn)字體桅锄,為該默認(rèn)字體創(chuàng)建styleNORMAL琉雳、BOLD样眠、ITALIC、BOLD_ITALIC翠肘,weight400的四個(gè)Typeface對(duì)象保存到sDefaults數(shù)組中吹缔,為/system/etc/font.xml文件中名稱為sans-serif、serif锯茄、monospacefamily節(jié)點(diǎn)創(chuàng)建styleNORMALweight400的Typeface對(duì)象SANS_SERIF茶没、SERIF肌幽、MONOSPACE

2.5.2 在layout文件中通過android:typeface屬性設(shè)置系統(tǒng)字體

目前通過這種方式可以設(shè)置如下幾種系統(tǒng)字體抓半,即2.5.1中初始化的四種系統(tǒng)字體:
noraml 普通字體,系統(tǒng)默認(rèn)使用的字體
sans 非襯線字體
serif 襯線字體
monospace 等寬字體

2.5.3 App中使用自定義字體

在Android是使用android:fontFamily屬性喂急,關(guān)于該屬性如何使用,可以看下面4.1的內(nèi)容笛求,由于該屬性是給TextView使用的廊移,所以就要從TextView的源碼開始分析,先看一下下面的流程圖:


上圖就是android:fontFamily屬性的解析過程探入,有興趣的同學(xué)可以自己對(duì)著上圖看源碼狡孔,這里概括一下整個(gè)流程:
1> 解析res/font/font_family.xml文件,根據(jù)TextView的android:textStyle屬性的值找到最匹配的font節(jié)點(diǎn)(即上面的第28步)
2> 根據(jù)該font節(jié)點(diǎn)對(duì)應(yīng)的字體庫創(chuàng)建Typeface對(duì)象蜂嗽,然后設(shè)置TextView的字段mTextPaint的字體為該Typeface對(duì)象

3 color-emoji的使用

color-emoji是Google開源的工具苗膝,用來將png類型的圖片生成字體庫,首先下載在https://github.com/googlei18n/color-emoji地址下載源碼植旧,然后進(jìn)入到color-emoji/examples/FruityGirl目錄下,目錄結(jié)構(gòu)如下所示:

$ tree -L 2
.
├── FruityGirl.tmpl.ttx.tmpl
├── Makefile
└── png
    ├── F000.png
    ├── F001.png
    ├── F002.png
    ├── F003.png
    ├── F004.png
    ├── F005.png
    ├── F006.png
    ├── F007.png
    ├── F008.png
    ├── F009.png
    ├── F00A.png
    ├── F00B.png
    ├── F00C.png
    ├── F00D.png
    ├── F00E.png
    ├── F00F.png
    ├── F010.png
    ├── F011.png
    ├── F012.png

上面又一個(gè)Makefile的文件辱揭,這個(gè)文件編譯過android系統(tǒng)源碼的應(yīng)該都很熟悉了,就是通過make命令來開始編譯操作病附,png目錄下的圖片就是用來生成字體庫的问窃,圖片的名稱就是生成的字體庫中與之對(duì)應(yīng)的Unicode編號(hào),將生成的字體庫應(yīng)用到TextView或者EditText上后完沪,就可以使用該編號(hào)來顯示對(duì)應(yīng)的圖片域庇。

首先將目錄中原來的png圖片都刪除掉,然后將上面五張表情包拷貝到png目錄下丽焊,查了五個(gè)字(與上面的五張表情包惺惺相惜)的Unicode編碼:傻(50BB)较剃、吻(543B)、嘿(563F)技健、微(5FAE)写穴、白(767D)。

執(zhí)行make命令雌贱,然后看一下目錄結(jié)構(gòu):

$ tree -L 2
.
├── FruityGirl.tmpl.ttf
├── FruityGirl.tmpl.ttx.tmpl
├── FruityGirl.ttf
├── Makefile
└── png
    ├── 50BB.png
    ├── 543B.png
    ├── 563F.png
    ├── 5FAE.png
    └── 767D.png

可以看到多了一個(gè)FruityGirl.ttf文件啊送,這就是包含上面五張圖片的字體庫偿短。

4 在Android中使用2中生成的字體庫

舉個(gè)例子,首先看一下運(yùn)行效果:



上面截圖中就是一個(gè)普通EditText控件馋没,只不過是應(yīng)用了2中生成的字體庫昔逗,當(dāng)輸入傻、吻篷朵、嘿勾怒、微和白五個(gè)漢字時(shí),就會(huì)顯示上面的五張圖片声旺。

4.1 將字體庫添加到Android工程中

Android 8.0(API級(jí)別26)引入了一項(xiàng)新功能笔链,在XML文件中的聲明字體庫,即把字體庫文件當(dāng)作資源文件腮猖,以在res/font/文件夾中添加字體庫文件的方式來將字體庫文件作為資源文件鉴扫,這些字體庫文件被編譯到R文件中并且借助新的資源類型訪問字體資源,例如澈缺,要訪問字體資源坪创,請(qǐng)使用@font/myfontR.font.myfont

要在運(yùn)行Android 4.1(API級(jí)別16)及更高版本的設(shè)備上使用該新功能姐赡,請(qǐng)使用the Support Library 26莱预。
要將字體庫添做為資源,首先在res目錄下創(chuàng)建font文件夾项滑,如下圖所示:


點(diǎn)擊OK按鈕锁施,然后將字體庫文件放到該目錄下,如下圖所示的目錄結(jié)構(gòu)將生成R.font.test:

上圖中的test.ttf字體庫文件就是3中生成的FruityGirl.ttf字體庫文件重命名得到的

4.2 創(chuàng)建font_family.xml文件

接著在font目錄下創(chuàng)建font_family.xml文件杖们,font_family.xml文件包含一個(gè)或多個(gè)字體庫文件及其style和weight悉抵,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@font/font_family">
    <font
        android:font="@font/test"
        android:fontStyle="normal"
        android:fontWeight="400" />
</font-family>

關(guān)于android:fontStyleandroid:fontWeight在2中已經(jīng)講過了。

4.3 在layout文件中使用

接著就是在layout文件中使用font_family.xml文件定義的字體了:

Activity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="80dp">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="36dp"
        android:fontFamily="@font/test"/>
</LinearLayout>

使用android:fontFamily屬性給EditText設(shè)置字體庫就可以了摘完,是不是很簡(jiǎn)單姥饰,這才是我追求的解決方案。

5 參考

  1. Android中使用ttf字體庫官方文檔
  2. CSS Fonts Module Level 3
  3. Font
  4. 自定義 View 1-3 drawText() 文字的繪制
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孝治,一起剝皮案震驚了整個(gè)濱河市列粪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谈飒,老刑警劉巖岂座,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異杭措,居然都是意外死亡费什,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門手素,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸳址,“玉大人瘩蚪,你說我怎么就攤上這事「迨颍” “怎么了疹瘦?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)巡球。 經(jīng)常有香客問我言沐,道長(zhǎng),這世上最難降的妖魔是什么酣栈? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任呢灶,我火速辦了婚禮,結(jié)果婚禮上钉嘹,老公的妹妹穿的比我還像新娘。我一直安慰自己鲸阻,他們只是感情好跋涣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸟悴,像睡著了一般陈辱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上细诸,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天沛贪,我揣著相機(jī)與錄音,去河邊找鬼震贵。 笑死利赋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猩系。 我是一名探鬼主播媚送,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼寇甸!你這毒婦竟也來了塘偎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤拿霉,失蹤者是張志新(化名)和其女友劉穎吟秩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绽淘,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涵防,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沪铭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片武学。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祭往,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出火窒,到底是詐尸還是另有隱情硼补,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布熏矿,位于F島的核電站已骇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏票编。R本人自食惡果不足惜褪储,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慧域。 院中可真熱鬧鲤竹,春花似錦、人聲如沸昔榴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽互订。三九已至吱肌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仰禽,已是汗流浹背氮墨。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吐葵,地道東北人规揪。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像温峭,于是被迫代替她去往敵國(guó)和親粒褒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1诚镰、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,980評(píng)論 3 119
  • 1. Stop Dancing Around Criticism and Put It to Use with T...
    kid551閱讀 398評(píng)論 0 1
  • 每個(gè)人的一生中都有一些無法忘記的美好回憶奕坟。隨著歲月的推移,有些人清笨,有些事月杉,卻仍牽動(dòng)于心。 我也不例外抠艾,在特殊的時(shí)間...
    人生由你閱讀 285評(píng)論 1 3
  • 揭諦苛萎!揭諦!波羅揭諦!波羅僧揭諦腌歉!菩提薩婆訶蛙酪! --摘自《心經(jīng)》學(xué)書作品,20181020
    ycongcong閱讀 288評(píng)論 0 3