起因
C端活动页项目需要用到 UI 同学给的一套艺术字体文件。为了减少包体积,该字体文件(TTF格式)已经经过一次字符的筛选(大约53kb),筛选出了常用的数字、字母、营销常用汉字大约280个字符左右。后台需要在活动页配置时艺术字的校验,若配置的字符不在该字体文件内,则不允许用户进行配置。
通过这个需求的开发,学习一些字体的相关知识。
字体基础
字体标准
字体就像 JavaScript 语言有自己的 ECMAScript 标准一样有着自己的行业标准。Apple 和 Microsoft 在1991年共同推出了 TrueType 标准,在此标准之前,还有 Adobe 公司于 1984 年发布了 PostScript 语言。1996 年微软联合 Adobe 在 TrueType 的基础上推出了 OpenType 字体标准,该标准整合了 PostScript 字体和 TrueType 字体的特点,并新增了许多新的特性。OpenType 陆续得到苹果和谷歌等公司的支持,在主流计算机平台应用广泛,目前已经成为字体设计的主要发展趋势。对于我们普通开发者而言,TrueType (即 ttf 后缀的字体文件)和 OpenType (可以采用 otf,ttf,otc,ttc 等多种格式) 比较常见。此外,还有专用于网页的网页开放字体(Web Open Font Format)格式,其设计标准通常为 OpenType 或 TureType,但在文件打包和压缩中专门为网络传输做了优化。WOFF 字体在主流浏览器中都得到很好的支持,文件后缀名为 .woff
以及 .woff2
。
字体显示方式及加载
字体显示,即使用 CSS 属性 font-family,这个属性前端开发者们相对都比较熟悉了。MDN 上是这么描述的:允许你通过给定一个有**先后顺序**的,由**字体名或者字体族名**组成的列表来为选定的元素设置字体。先后顺序指的是,当我们对一个字符进行字体的选择时,我们会从 font-family 中的从前到后选定能够使用的字体,这里字体的选择的单位是逐字符的,有可能一个字符周围都是另外一个 A 字体,而此字符由于 A 字体无法正常显示(可能由于 A 字体 仅仅某些特定的 样式 、变体或大小下有效时),使用了 B 字体。
我们应当至少在使用的 font-family 列表中添加一个通用的字体族名。通用的字体族名包含一些常见内置在电脑中的字体。比如 带衬线字体 serif
,无衬线字体 sans-serif
等等。
css
代码解读
复制代码
/* 使用通用字体族sans-serif中的Gill Sans Extrabold和Goudy Bookletter 1911 */
font-family: "Gill Sans Extrabold", sans-serif;
font-family: "Goudy Bookletter 1911", sans-serif;
/* 使用一个通用字体族名 */
font-family: serif;
font-family: sans-serif;
接下来我们来说说自定义字体。自定义字体加载最常用也是我们最熟悉的方式就是利用 CSS 的 @font-face
属性。字体通过这个属性能用户本地安装的字体加载(src 属性中的 local 函数)或者从远程服务器加载(url 函数)。
css
代码解读
复制代码
@font-face {
font-family: "Roboto";
src: local("Roboto"),
url("/fonts/Roboto/Roboto-Regular.woff2") format("woff2"),
url("/fonts/Roboto/Roboto-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: auto;
unicode-range: U+000-5FF;
}
此 @font-face 指定了一个名为 Roboto 的自定义字体。注册和加载完相关的自定义字体后,即可以通过 font-family 属性加载此字体。
注意,unicode-range 其作用是告知浏览器,通过 @font-face 引入的字体覆盖了 unicode 字符体系的哪些部分,以便浏览器仅在该范围内使用该字体。这个 unicode-range 算是个生僻的属性,有兴趣的可以戳查看更多相关解析。
解决方案
设计给了我一个 ttf 格式的字体文件,我该如何提取出文件中对应的字符呢?毕竟我需要在后台做一些用户输入校验。此时一个专用于处理字体的 JS 库就派上用途了。即 opentype.js「https://github.com/opentypejs/opentype.js」 库,该库提供了从浏览器或者 Node.js 两种访问方式,提供很多实用方便的方法让我们对字体文件进行解析和处理、剪裁,重绘等等。此时将字体文件上传至 CDN 地址后,在 C 端通过 @font-face 引入远程字体,使用无问题。后台可以通过 fetch 方法可以拿到该字体文件,然后通过 opentype 库对字体文件进行解析。
通过 opentype 的 parse 方法解析出字体库,得到 font 对象。
在此对象中,我们能拿到所有字符的字形表,即由所有字体文件的 Glyph 对象组成的集合。
其中,glyphs 对象下的 glyphs 属性保存了所有字形的集合,我们可以将它们进行收集。
Glyph 对象是字形的一组属性集合,glyph 为单个需要渲染的字形,是渲染的最小单位。字形是通常与字符相对应的单个标记。一个抽象字符可能有多种形状(字形),但由于都具有相同的含义,在 Unicode 中被视为同一个字符,只会分配一个码点,比如汉字有楷、行、草、隶等写法,这些属于同一个字符的不同字形。所以我们拿到 Unicode 唯一编码即可,然后和我们需要校验的字符的 Unicode 编码进行比较。
除此之外,这个库还提供了许多其他关于字体的方法,只是我的需求中暂时用不到。
附上代码:
javascript
代码解读
复制代码
import opentype from 'opentype.js'
javascript
代码解读
复制代码
// 读取后台配置化接口拿到字体CDN并转化为二进制buffer形式
getFontData() {
return new Promise((resolve, reject) => {
this.$tHttp
.post('xxx后台配置化接口')
.then((res) => {
const { code, data } = res;
if (code === 10000) {
// 拿到配置化接口中配置的字体CDN地址
const { fontCDNUrl } = data;
// fetch 获取字体 CDN 并解析
fetch(fontCDNUrl)
.then(async response => {
// 检查响应是否成功
if (!response.ok) {
this.$Message.error('字体接口获取失败,请联系管理员!');
throw new Error('网络响应不是OK');
}
const buffer = response.arrayBuffer();
// 转化为二进制 buffer
const fontBuffer = await buffer;
// 解析为JSON格式
resolve(fontBuffer);
})
.catch(error => {
// 处理错误
this.$Message.error('字体接口获取失败,请联系管理员!');
console.error('获取数据时出现问题:', error);
});
}
}).catch((err) => {
this.$Message.error('字体接口获取失败,请联系管理员!');
reject(err);
});
});
},
javascript
代码解读
复制代码
const StaticCharSet = new Set();
/**
* 校验函数,校验字符串是否合法
* @param titleNeedValid 需要校验的字符串
*/
async validCorrectFontFile(titleNeedValid) {
if (!StaticCharSet.size) {
// 读取文件转为 buffer
const fontFileBuffer = await this.getFontData();
const font = opentype.parse(fontFileBuffer);
// 获取 cmap(字符映射)表
const glyphs = font.glyphs.glyphs;
for (const key in glyphs) {
const unicode = glyphs[key]['unicode'];
if (unicode) {
StaticCharSet.add(unicode);
}
}
const SPACEUNICODE = ' '.codePointAt(0); // 空格 - 值为 32
if (!StaticCharSet.has(SPACEUNICODE)) {
// Unicode 32 为空格,UI给的字体文件中空格 Unicode 不合法,这里需要手动塞入,且空格在C端不同字体不会影响到样式
StaticCharSet.add(SPACEUNICODE);
}
}
const unValidUnicodeIndex = [];
// 检查字符串中的字符是否在 Unicode 集中
for (let index = 0; index < titleNeedValid.length; index++) {
const code = titleNeedValid[index].codePointAt(0); // 获取字符的 Unicode 编码
if (!StaticCharSet.has(code)) {
unValidUnicodeIndex.push(index); // 如果有任何字符不在 Unicode 集中,返回 false
}
}
return unValidUnicodeIndex; // 字符串中的所有字符都在 Unicode 集中,返回 true
}
如有侵权请联系站点删除!
Technical cooperation service hotline, welcome to inquire!