android字体工作原理

Android字体工作原理

本文简单介绍了上层如何使用特定字体,android字体工作原理提出要求浏览器使用单独的字体,并且不影响系统默认字体的使用。图形小组在该需求的基础上,又提出了如果一个ttf文件包含多个字库,skia是否支持,如果不支持,实现难度多大。本文主要在这两个方面展开。

2.1 问题分析

针对以上需求,主要从以下两个方面展开:

1.       android是否提供设置特定字体的标准API供应用使用

2.       android字体工作原理

经过调研发现,android本身是支持应用程序设置特定字体,并且不影响其他应用程序的显示。所以问题主要落在android字体工作原理上面,该问题由于android4.0之后工程不再附带skia的源码,使得的问题又多了一层,以下将详细介绍。

2.2  Android设置特定字体API

设置特定字体,一般有如下步骤:

1.       创建字体

2.       设置字体

 

android.graphics.Typeface提供了3个API供应用程序创建特定字体:

1.       Typeface createFromAsset(AssetManager mgr, String path);

2.       Typeface createFromFile(File path);

3.       Typeface createFromFile(String path);

以及如下3个API来创建系统字体:

1.       Typeface create(String familyName, int style);

2.       Typeface create(Typeface family, int style);

3.       Typeface defaultFromStyle(int style);

 

系统字体放在/system/fonts目录下,可以通过查看/system/etc/system_fonts.xml来了解系统当前支持的字体文件及其名字对应关系。

图1 system_fonts.xml部分内容截图

nameset代表该font family可以有的名字,fileset表示该font family所对应的ttf文件,由上至下分别代表正常、粗体、斜体、粗斜体所对应的ttf文件, android skia会根据这些来初始化相应的变量。

创建完字体之后,则可以通过android.graphics.PaintTypefacesetTypeface(Typeface typeface);来设置。

代码示例:

typeface =Typeface.createFromFile("/system/fonts/XXX.ttf");   

paint.setTypeface(typeface);

2.3 Android字体工作原理

2.3.1 Android skia源码下载

android 4.0上不再有skia源码,可以前往如下地址下载源码:

http://code.google.com/p/skia/

同时如下网址讲解了如何在android上下载skia源码并编译,在此不再赘述。

https://sites.google.com/site/skiadocs/user-documentation/quick-start-guides/android

2.3.2 Android系统字库加载

android字体由android2D图形引擎skia实现,并在ZygotePreloading classes中对系统字体进行load

相关涉及到:

android的启动过程

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中的preloadClasses方法,对/framework/base/preloaded-classes文件里面的类一一加载Class.forName("android.graphics.Typeface");

Class.forName()会加载类到DVMJVM),同时加载static代码块。

 

*****************************************************************************************

java中class.forName和classLoader都可用来对类进行加载。前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。而classLoader只干一件事情,就是.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。

*****************************************************************************************

android.graphics.Typeface Static代码:

 

static {

       DEFAULT         = create((String)null,0);

       DEFAULT_BOLD    = create((String)null, Typeface.BOLD);

       SANS_SERIF      = create("sans-serif", 0);

       SERIF           =create("serif", 0);

        MONOSPACE      = create("monospace", 0);

 

        sDefaults = newTypeface[] {

           DEFAULT,

           DEFAULT_BOLD,

           create((String)null, Typeface.ITALIC),

           create((String)null, Typeface.BOLD_ITALIC),

        };

 

public static Typeface create(String familyName, int style) {

        return newTypeface(nativeCreate(familyName, style));

}

 

nativeCreate()是jni方法,其实现在Typeface.cpp和skFontHost_android.cpp,其中后者是skia针对android平台字体实现的port。以下是Typeface.cpp native方法的注册。

由此可以知道,在JAVA层默认会创建sans-serif,serif,monospace三种字体,并且通过create第一个参数为null,来创建默认字体的四种style:normal,bold,italic,bolditalic。

Typeface_create又进一步调用face = SkTypeface::CreateFromName(str.c_str(),style);来完成。

SkTypeface*SkTypeface::CreateFromName(const char name[], Style style) {

    return SkFontHost::CreateTypeface(NULL,name, style);

}

 最终会调用到SkFontHost_android.cpp中的createTypeface。该函数做了两件事:

1.      首先调用load_system_fonts()加载系统字库,该函数会首先判断相关的系统字库变量没有被初始化,没有则加载,否则什么都不做,load_system_fonts()加载的字库是由/system/etc/system_fonts.xml来进行配置的。

2.       在当前的系统字库里面查找与所要求最接近的字体,并返回

 

由此JAVA层与C层联系起来,而创建系统字库,对与JAVA层来说也只是返回C层的一个ref。可以用图2来表示android系统字库加载过程:

 

Zygote preloadClasses() -> Class.forName("android.graphics.Typeface")–> 执行Typeface.javastatic块 -> create创建默认字体 -> Typeface.cpp Typeface_create–> SkTypeface.cpp CreateFromName() -> SkFontHost.cpp load_system_fonts()加载系统字体

 

2.3.3 SkFontHost_android.cpp重要结构体及方法

重要结构体介绍:

struct FontFamily {

    SkTDArray<constchar*>  fNames;

   SkTDArray<const char*> fFileNames;

    int order;

};

解析system_fonts.xml得到的结构体,fNames保存文件名称信息,fFileNames保存相应的文件名。

struct FontInitRec {

    constchar*         fFileName;

    const char*const*  fNames;     // null-terminated list

};

FontFamily转换后的结构体,同一个fontfamily第一个ttf文件保存所有的名称,其余的fNames为NULL。

struct FamilyRec {

    FamilyRec*  fNext;

    SkTypeface* fFaces[4];

 

    FamilyRec()

    {

        fNext = gFamilyHead;

        memset(fFaces, 0,sizeof(fFaces));

        gFamilyHead = this;

    }

};

保存同一个font family的节点,每个font family分配了四个face,分别对应为normal,bold,italic,bolditalic。

struct NameFamilyPair {

    const char* fName;      // we own this

    FamilyRec*  fFamily;   // we don't own this, we just reference it

 

    void construct(const charname[], FamilyRec* family) {

        fName = strdup(name);

        fFamily = family;   // we don't own this, so just record thereferene

    }

 

    void destruct() {

        free((char*)fName);

        // we don't own family, sojust ignore our reference

    }

};

记录name跟font family的对应关系。

重要方法流程:

SkFontHost::CreateTypeface(constSkTypeface* familyFace,                                      constchar familyName[], SkTypeface::Style style)流程:

1.       调用init_system_fonts初始化系统字体

2.       在当前全局列表中查找最接近的typeface并返回

 

init_system_fonts流程:

1.       调用load_font_info初始化相关变量

2.       调用get_name_and_style获取字体的属性,name style

3.       通过这些属性创建FileTypeface,并把这些字体信息保存到全局列表中

4.       将family 及 name信息添加进NameFamilyPairList中

其中步骤2 3 4是循环执行,直到所有的信息都加入进去。

 

load_font_info流程:

1.             调用getFontFamilies(fontFamilies);解析/system/etc/system_fonts.xml、/system/etc/fallback_fonts.xml、/vendor/etc/fallback_fonts.xml文件,并把相应信息保存在fontFamilies。fallback_fonts.xml是当相应的字库找不到时,会继续找的字体,vendor一般为第三方厂商提供。fontFamilies保存了ttf文件的文件名字及名称。

2.             将fontFamily结构转换成FontInitRec结构,主要作用是:同一个fontfamily第一个出现的字体保存所有的名称,后续字体的名称均设置为NULL,以标致是同一个font family。

3.             将转换后的结果保存在gSystemFonts,并用gNumSystemFonts记录当前系统字体个数。

   

由此可知,通过名称或者familyface来创建Typeface的API,一般用于系统字体的创建,因为系统字体是一定会在列表中的,自定义字体则不一定会在列表中,主要看该字体之前是否被打开过,且没有被删除,而如果没有在列表中,则会选一个跟所需要的字体比较接近的字体来返回。

 

SkFontHost::CreateTypefaceFromFile(const char path[])流程:

1.       调用SkMMAPStream函数将指定文件映射到内存空间,以进程共享读的方式

2.       调用SkFontHost::CreateTypefaceFromStream(stream);创建字体

 

SkFontHost::CreateTypefaceFromStream(SkStream* stream)流程:

1.       调用find_name_and_attributes获取字体style

2.       调用init_system_fonts初始化系统字体

3.       调用StreamTypeface构造函数创建Typeface,并把信息保存到相应的链表中。

 

这里有一个疑问,本身SkMMAPStream是以进程共享的方式映射的,为什么在将相应信息保存到链表中的时候,不去查询链表中是否已经存在该typeface,如果多次打开同一个文件,则会导致链表中同一typeface具有多个节点。

2.3.4同一个ttf包含多个fontfamily是否支持

skia目前只提供了2.3.2中的5个API供应用程序调用,到skia的so库中就统一成三个API,也就是上一节介绍的CreateTypeface、CreateTypefaceFromStream、CreateTypefaceFromFile,不管是哪个方法都首先会调用find_name_and_attributes获取字体style及相关信息,之后再在这些信息的基础上构建typeface相关变量,而find_name_and_attributes是通过FT_Open_Face(library, &args,0, &face)来构建font face,从而获取font face的信息,注意第三个参数font_index是写死为0的,这也意味着skia在android上目前是不支持ttc文件及ttf文件里包含多个style的用法的。这一点也可以在SkFontHost::GetFileName注释中得到佐证(该API是通过fontId来获取ttf文件名及该font在ttf里面的Index):

如果要支持该功能,则需扩展相应的结构体,获取ttf内部的总的face num,并保存相应的偏移信息。

 

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在推上fo了个toefl单词机器人,没想到很多单词的音标显示成了小方块,那就是android默认的英文字体对某些英语国际音标不支持了,当然我的Galaxy S刷了第三方ROM的,不过从网上的帖子来看,官方的字体存在一样的问题,总而言之是字体的问题了。 也搜了一些帖子,但没有很好的解决方案,默认的英文字体还是挺美观的,只是某些英语音标的编码位上缺失了相应的字符而已,Anroid使用Java作为默认开发环境,那默认的字体也应该是使用UNICODE UCS编码的,事实证明的却如此。那就很简单了,强大的字体编辑工具FontForge派上用场了。 Ubuntu仓库里面的版本太低了,可以使用GetDeb仓库来安装最新版本的FontForge。用FontForge打开Android默认的英文字体DroidSans.ttf,果然国际音标编码区域空空如也,英语音标现实不完全也就一点儿也不奇怪了。从国际音标wiki上查找到了这写符号的UNICODE编码,从DejaVuSerif.ttf和Gothic.ttf这两个字体里面提取了对应的字形(glyph)插入到DroidSans.ttf相应的BMP(Basic Multilingual Plane)编码位上。当然只补充了英语音标会用到的字符,包括ɑ,ɒ,ɔ,ə,ɛ,ɜ,ɪ,ɵ,ʃ,ʌ,ʒ,ʤ,ʦ,ʧ这几个常用音标字符。默认的字体竟然连重音(primary stress)和次重音(Secondary stress)这两个符号都没有,一并补齐了。 将制作好的字体覆盖Android默认英文字体/system/fonts/DroidSans.ttf,当然需要root权限,再看英语音标,显示的相当完美了,google dictionary里面的音标也完全没有问题。
Android Strongbox是一种安全芯片(TEE)的实现,用于提供安全的密钥存储和加密功能。它的工作原理可以概括为以下几个步骤: 1. 初始化:Strongbox在设备启动时进行初始化。它与安全芯片建立通信通道,并验证芯片的完整性和安全性。这个通道将用于后续的密钥生成、存储和操作。 2. 密钥生成和存储:Strongbox负责生成和存储密钥。应用程序可以通过API调用Strongbox来生成不同类型的密钥。Strongbox通过与安全芯片进行交互,在芯片内部生成和存储密钥对。这确保了密钥的安全性,因为密钥不会离开安全芯片。 3. 加密和解密:应用程序可以使用Strongbox进行加密和解密操作。Strongbox将加密和解密请求传递给安全芯片,由芯片内部的安全执行环境(TEE)负责实际的加密和解密操作。这样可以确保数据在加密和解密过程中得到保护,并且密钥不会泄露。 4. 密钥保护和访问控制:Strongbox负责保护存储在安全芯片中的密钥,并提供访问控制机制。只有授权的应用程序才能访问Strongbox中的密钥,并且只能执行特定的操作,如加密、解密或签名。这种访问控制机制确保了密钥的安全性和隐私性。 5. 密钥生命周期管理:Strongbox提供API用于管理密钥的生命周期。应用程序可以调用这些API来生成、导入、删除和销毁Strongbox中的密钥。这使得应用程序能够灵活地管理密钥,以适应不同的安全需求。 6. 安全芯片状态监测:Strongbox负责监测安全芯片的状态。它定期检查芯片的完整性和安全性,并提供错误处理机制来处理异常情况。这确保了Strongbox芯片的可靠性和安全性。 总体而言,Android Strongbox通过与安全芯片进行通信和交互,提供了安全的密钥存储和加密功能。它生成、存储和保护密钥,并提供访问控制和错误处理机制,以确保系统的安全性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值