标签(空格分隔): ttf 、字体提取
场景:
如图,将“keep moving”区域的文案可以动态修改字体样式,“keep moving”文案也是可以动态修改
方案:
1. 将字体文件打包放在App里面
这种方案就需要考虑字体文件大小,一般的字体文件大小如下:
英文字体:一般是几百KB
中文字体:几M ~ 十几M
显然,这种方案不管是中文还是英文字体,都增大app安装包。
2. 服务端提供字体文件,APP下载使用
2.1 如果是纯英文字体,不介意几百KB的下载流量,可以使用谷歌Android O提供的字体下载工具。
private void requestDownload(String familyName) {
QueryBuilder queryBuilder = new QueryBuilder(familyName)
.withWidth(progressToWidth(mWidthSeekBar.getProgress()))
.withWeight(progressToWeight(mWeightSeekBar.getProgress()))
.withItalic(progressToItalic(mItalicSeekBar.getProgress()))
.withBestEffort(mBestEffort.isChecked());
String query = queryBuilder.build();
Log.d(TAG, "Requesting a font. Query: " + query);
FontRequest request = new FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
query,
R.array.com_google_android_gms_fonts_certs);
FontsContractCompat.FontRequestCallback callback = new FontsContractCompat
.FontRequestCallback() {
@Override
public void onTypefaceRetrieved(Typeface typeface) {
mDownloadableFontTextView.setTypeface(typeface);
}
@Override
public void onTypefaceRequestFailed(int reason) {
}
};
FontsContractCompat
.requestFont(MainActivity.this, request, callback,
getHandlerThreadHandler());
}
2.2 如果有中文字体,由于中文字体文件过大,可以进行字体提取
字体结构
这是一个已经较为复杂的衬线体拉丁字母,141 个控制点。
这是一个笔画不算太多的宋体汉字,181 个控制点。
由于字体文件的发展,目前的字体文件存在 直线和二次 B - 样条 (Bezier2Spline) 曲线、点阵字体、矢量字体和向量轮廓字体
字体文件结构
2.2.1 如果在字体中使用 TrueType 或 Postscipt 轮廓信息 ,以下表是实现字体功能所必需的。
标志位 | 名 称 | 描 述 |
---|---|---|
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 | OS/ 2 和 Windows 度量信息 |
Post | PostScript information | PostScript 打印机控制 |
2.2.2 与 TrueType 轮廓描述信息有关的表
标志位 | 名 称 | 描 述 |
---|---|---|
Cvt | Control Value Table | 控制值表 |
Fpgm | Font program | 字体程序区 |
Glyf | Glyph data | 轮廓描述信息 |
Loca | Index to location | 轮廓数据索引表 |
prep | CV T Program | CV T 程序区 |
2.2.3 与 Postscipt 轮廓描述信息有关的表
标志位 | 名 称 | 描 述 |
---|---|---|
CFF | PostScript font program (compact font format) | PostScript 字体程序区(压缩字体格式) |
Fvar | Apple’s font variations table | Apple 字体转换表 |
MMSD | Multiple master supplementary data | 多主扩充数据 |
MMFX | Multiple master font metrics | 多主字体度量信息 |
2.2.4 支持垂直显示等高级字体印刷功能的表
标志位 | 名 称 | 描 述 |
---|---|---|
BASE | Baseline data | 基线数据 |
GDEF | Glyph definition data | 笔划定义数据 |
GPOS | Glyph positioning data | 笔划位置数据 |
GSUB | Glyph substitution data | 笔划替换数据 |
J STF | J ustification data | 调整数据 |
2.2.5 与位图笔划有关的表
标志位 | 名 称 | 描 述 |
---|---|---|
EBDT | Embedded bitmap data | 嵌入式位图数据 |
EBLC | Embedded bitmap location data | 嵌入式位图位置数据 |
EBSC | Embedded bitmap scaling data | 嵌入式位图缩放数据 |
2.2.6 其它一些 OpenType 描述表
标志位 | 名 称 | 描 述 |
---|---|---|
DSIG | Digital signature | 数字标记 |
Gasp | Grid - fitting/ Scan - conversion | 网格适配/ 扫描转换过程表 |
Hdmx | Horizontal device metrics | 水平设备度量信息 |
Kern | Kerning | 紧排控制表 |
L TSH | Linear threshold data | 线性门限数据 |
PCL T | PCL 5 data | PCL 5 页面描述语言数据 |
VDMX | Vertical device metrics | 垂直设备度量信息 |
Vhea | Vertical Metrics header | 垂直度量头信息 |
vmtx | Vertical Metrics | 垂直度量信息 |
从文件中提取字体,主要要用到的几个表
- cmap 字符代码到图元的映射 把字符代码映射为图元索引
- glyf 图元数据 图元轮廓定义以及网格调整指令
- loca 位置表索引 把元索引转换为图元的位置
具体提取字体代码 参考
public void subsetFontFile(File fontFile, File outputFile, int nIters) throws IOException {
FontFactory fontFactory = FontFactory.getInstance();
FileInputStream fis = null;
try {
fis = new FileInputStream(fontFile);
byte[] fontBytes = new byte[(int)fontFile.length()];
fis.read(fontBytes);
Font[] fontArray = null;
fontArray = fontFactory.loadFonts(fontBytes);
Font font = fontArray[0];
List<CMapTable.CMapId> cmapIds = new ArrayList<CMapTable.CMapId>();
cmapIds.add(CMapTable.CMapId.WINDOWS_BMP);
byte[] newFontData = null;
Font newFont = font;
if (subsetString != null) {
Subsetter subsetter = new RenumberingSubsetter(newFont, fontFactory);
subsetter.setCMaps(cmapIds, 1);
List<Integer> glyphs = GlyphCoverage.getGlyphCoverage(font, subsetString);
subsetter.setGlyphs(glyphs);
Set<Integer> removeTables = new HashSet<Integer>();
// Most of the following are valid tables, but we don't renumber them yet, so strip
removeTables.add(Tag.GDEF);
removeTables.add(Tag.GPOS);
removeTables.add(Tag.GSUB);
removeTables.add(Tag.kern);
removeTables.add(Tag.hdmx);
removeTables.add(Tag.vmtx);
removeTables.add(Tag.VDMX);
removeTables.add(Tag.LTSH);
removeTables.add(Tag.DSIG);
removeTables.add(Tag.vhea);
// AAT tables, not yet defined in sfntly Tag class
removeTables.add(Tag.intValue(new byte[]{'m', 'o', 'r', 't'}));
removeTables.add(Tag.intValue(new byte[]{'m', 'o', 'r', 'x'}));
subsetter.setRemoveTables(removeTables);
newFont = subsetter.subset().build();
}
if (strip) {
Subsetter hintStripper = new HintStripper(newFont, fontFactory);
Set<Integer> removeTables = new HashSet<Integer>();
removeTables.add(Tag.fpgm);
removeTables.add(Tag.prep);
removeTables.add(Tag.cvt);
removeTables.add(Tag.hdmx);
removeTables.add(Tag.VDMX);
removeTables.add(Tag.LTSH);
removeTables.add(Tag.DSIG);
removeTables.add(Tag.vhea);
hintStripper.setRemoveTables(removeTables);
newFont = hintStripper.subset().build();
}
FileOutputStream fos = new FileOutputStream(outputFile);
if (woff) {
WritableFontData woffData = new WoffWriter().convert(newFont);
woffData.copyTo(fos);
} else if (eot) {
WritableFontData eotData = new EOTWriter(mtx).convert(newFont);
eotData.copyTo(fos);
} else {
fontFactory.serializeFont(newFont, fos);
}
} finally {
if (fis != null) {
fis.close();
}
}
}
public static List<Integer> getGlyphCoverage(Font font, String string) {
CMapTable cmapTable = font.getTable(Tag.cmap);
CMap cmap = getBestCMap(cmapTable);
Set<Integer> coverage = new HashSet<Integer>();
coverage.add(0); // Always include notdef
// TODO: doesn't support non-BMP scripts, should use StringCharacterIterator instead
for (int i = 0; i < string.length(); i++) {
int c = (string.charAt(i)) & 0xffff;
int glyphId = cmap.glyphId(c);
touchGlyph(font, coverage, glyphId);
}
List<Integer> sortedCoverage = new ArrayList<Integer>(coverage);
Collections.sort(sortedCoverage);
return sortedCoverage;
}
private static void touchGlyph(Font font, Set<Integer> coverage, int glyphId) {
if (!coverage.contains(glyphId)) {
coverage.add(glyphId);
Glyph glyph = getGlyph(font, glyphId);
if (glyph != null && glyph.glyphType() == GlyphType.Composite) {
CompositeGlyph composite = (CompositeGlyph) glyph;
for (int i = 0; i < composite.numGlyphs(); i++) {
touchGlyph(font, coverage, composite.glyphIndex(i));
}
}
}
}
private static CMap getBestCMap(CMapTable cmapTable) {
for (CMap cmap : cmapTable) {
if (cmap.format() == CMapFormat.Format12.value()) {
return cmap;
}
}
for (CMap cmap : cmapTable) {
if (cmap.format() == CMapFormat.Format4.value()) {
return cmap;
}
}
return null;
}
private static Glyph getGlyph(Font font, int glyphId) {
LocaTable locaTable = font.getTable(Tag.loca);
GlyphTable glyfTable = font.getTable(Tag.glyf);
int offset = locaTable.glyphOffset(glyphId);
int length = locaTable.glyphLength(glyphId);
return glyfTable.glyph(offset, length);
}
疑问1:如果是中英文混合的文案怎么实现?
疑问2:QQ是怎么实现的?