前端:如何校验某个字符是否存在于字体文件?

前端:如何校验某个字符是否存在于字体文件?

技术博客 admin 139 浏览

起因

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!