字体,对于开发者而言,默认基本都是采用系统的字体,比如系统差别、中西文差别,还有最后的衬线字体,比如我们公司就喜欢用 android 的 Roboto 默认字体来显示数字。如果采用自己的字体的话,会把其放在最前面,所以最前面是 OPPOSANS, Roboto, Noto Sans CJK SC, Source Han Sans CN 后面两个是思源字体,毕竟 OPPOSANS 是和思源字体结合的。。。。
A context module is generated. It contains references to all modules in that directory that can be required with a request matching the regular expression. The context module contains a map which translates requests to module ids.
国庆后一场秋雨一场寒,属于东南季风的台风带来了明显的降温,又到了一个尴尬的温度,长袖短裤都有人穿。这个温度,感觉很舒服,尤其是在海边骑单车的时候,沿着沙河路的时候,城市灯光的点缀,观景台边的海涛声、阵阵袭人的秋意就来了。
本篇是介绍两个琐事,都是工作中遇到的,一个是可变字体探索,一个是
require
扫盲记。Variable Fonts
好像从前几个月开始,就接触了可变字体,以前设计推荐的是使用 5 种字体,你没有看错,在项目里面用到了 5 、种字体,不同的粗细,不同的高瘦,每个字体都基本在 8M 左右,通过不同的字体来展示设计的风格,真是。。。挺好的。今年开始有新的字体,没有以前的 5 种,只用一个可变字体了,通过一个字体来展示之前 5 个的字体,可以说很是优秀,当然对开发而言,统一的字体最是简单,而且一个字体意味着只要加载一种就好了,之前的要加载 5 种字体,虽然一个可变字体的体积是 20 M。
可以通过这个网站 玩一下可变字体。
字体,对于开发者而言,默认基本都是采用系统的字体,比如系统差别、中西文差别,还有最后的衬线字体,比如我们公司就喜欢用
android
的Roboto
默认字体来显示数字。如果采用自己的字体的话,会把其放在最前面,所以最前面是OPPOSANS, Roboto, Noto Sans CJK SC, Source Han Sans CN
后面两个是思源字体,毕竟OPPOSANS
是和思源字体结合的。。。。通过设置
font-variation-settings: "wght" 550
可以调整字体的粗细,比如OPPOSANS
字重可以调整到1000-1000
区间,实现无极调整,不像以前的字体,只有一百倍数的font-weight
,而且要一个字体文件就够了。还有其他的比如wdth
ital
这些都可以设置。 比如下图还能在这个基础上用上
font-weight
,当然这个就不规范了。目前font-variation-settings
的兼容性还是比较好的,除了 ie 和部分比较老的浏览器不支持外,其他都没有问题的。字体的普通处理
如果是采用系统的字体那一切都挺好的,但是作为设计,作为一家最求美感的公司,就是要有自己的字体,于是普通字体的 10M 的体积,加载速度就可以劝退大部分人了。为了平滑顺利过渡字体,一般采用的是如下几个方案:
font-face
定义的时,采用swap
来显示,系统会优先采用已有的字体,避免字体加载导致的阻塞,使得文字无法显示;当然这种方案会导致字体加载成功时,页面切换会从当前字体切换到自定义字体,导致用户体验稍差。如果自定字体体积小,可以不采用swap
方式。link
标签里面采用preload
的方式,字体资源在浏览器里,属于优先级较低的资源,通过link
的预加载可以显著的提高优先级,避免字体加载时间过长,导致切换时候带来的不好体验。溜
这个字,那就提取字体包里的溜
,这样字体文件就可以压缩的非常小了字体的提取历程
这里要介绍是可变字体的提取问题,先看看普通字体提取,之前用的是
font-spider
,使用下来可以满足字体的压缩,提取需要的子集,用法很方便,如下:再通过指令
font-spider ./test.html
就可以从source.eot
字体包里面压缩出仅仅包含溜
一个字的字体,当然会有一点小问题,比如垂直方向的行间距变小了,但是总体问题不大,10M 的字体包,最后只剩下几 kb。这个时候如果用软件 FontForge 查看的话,可以看到溜
保留下来了,其他被移除了。上面左边是正常的字形,右边则是压缩之后的效果,可以看到压缩后周围的小伙伴都被吓跑了。
只是到了可变字体,压缩就不是这样了,简简单单的
font-spider
打包出来的字体就不能用,会出现字体镂空的情况,而且关键是不能调整可变字体的wght
,设置了也不起作用,简直就是和普通字体差不多,不再是可变字体了。于是翻箱倒柜的,在
font-spider
里面转了一圈,结果发现里面处理字体的内容不多,更多的是对输入文件和样式处理,通过模拟的浏览器环境,自研的browser-x
(大佬自己写的Node.js
实现的虚拟浏览器) 来获取样式,保证不同的的font-family
打包出不同的字体,分析输入的参数,文字最后输出四种格式的字体,woff woff2 svg ttf
这些,当然在WebFont
里面看到了不少冗余的代码,一度让我误解了,比如weight stretch
这些属性,就不能使用。。。。可能也是大佬弃坑了吧,最后落实到压缩的还是fontmin
这个库,也有三方压缩的工具都是基于fontmin
的。fontmin
是一款中间件机制的字体处理工具,比如glyph
可以用来压缩字体,比如ttf2woff
可以转换字体。比如下面的官方例子:在
fontmin
代码里面的glyph
插件代码中可以看到如下形式:敢情
fontmin
也是套娃的。。。最后核心的字体处理还是要跑到fonteditor-core
里面,怎么说呢,fontmin
是一个优秀的集成商,有字体压缩,还有字体格式转换这些功能,虽然大部分是基于第三方的。而且不管是fontmin
还是font-spider
也有四五年没有更新主要内容了,作者也都弃坑了。那对于 16 年底才发布的可变字体,好像不支持也是可以理解的。table
介绍到
fonteditor-core
就要提一下table
的概念,这个是布局信息表,其包含了字形的位置、对齐、基线等等信息,字体文件则是由这一系列的表构成的,其中有部分表是可选的。字体目录是字体文件的指南,提供访问其他表所需的信息,包含两部分:偏移子表(offset subtable)和表目录(table directory)。偏移子表记录了字体文件中
table
的数量,并提供了快速访问表目录的方法。偏移子表后面就是表目录,表目录主要包含了表的tag
、校验、偏移、长度等信息,字体文件中的所有表都在表目录里面有入口。看了不少文档,每个文档对必选的
table
都有自己的解释,综合一下,下面是其中有几个是非常必要的table
:Unicode
,获得索引也就可以根据索引从字体中加载这个字形。上面几个表的介绍可能理解不太到位地方,因该差不多大致如此吧。另外,还有一些比如
OS/2
: 用于 windows 系统的配置,所以对跨平台的字体就非常需要了,但是若是针对 Mac 这些就不必了。具体的字形什么的,用
FontForge
软件打开任意一个字体就可以看到了,比如下面的:你甚至都可以修改字形。。。。
至于字体从加载到渲染出来的流程可以参考一下知乎上的介绍
上面介绍的是
cmap
根据字符代码拿到字形索引,再从loca
拿到索引对应的字形偏移,最后到glyf
加载字形的过程。loca
表可以参考以下图:每个字形都有自己长度,从而形成相对于
0
位置的偏移,而loca
表则是记录字形索引到字形偏移的映射表。当然这里面还有很多的表的内容没有谈到,比如和
TrueType
、CCF
、SVG
以及BitMap
相关的表,还有一个是高级表,比如GSUB
是glyf
的替换表,之前提到的一个字符代码最后可以映射到字形,但是如果是连字的时候,就不一定是简单的字形叠加,例如下面的:可以看到单独一个字的时候都是好好的,但是一旦结合在一起,就是不是
f + i = fi
了,而是有新的字形。这一点在阿拉伯语中也是的,字形在不同的位置有不同的显示。。。。。(原来阿拉伯语这么神奇,简直就是蝌蚪文)。除了高级表,还有色彩相关的,其他比较杂的,最后还有一个是
OpenType Font Variations
可变字体,也是OpenType
规范中,里面有avar
、cvar
、fvar
、gvar
、HVAR
、MVAR
、STAT
和VVAR
这几个。可变字体的 table
可变字体,如前面提到的,可以让设计者将多个字体合并为一个字体,下面的示意图很好的介绍了字重和字宽度变化导致字形的变化:
这里面看到的
width
和weight
都是fvar
表所描述的,用来存储轴的信息,以及命名实例,其中命名实例是可选的字段。轴的信息,比如wght(100-1000)
、width(10-200)
,包含了轴名称、最小最大值和默认值等,命名实例则是由轴与轴之间定下的命名的特定坐标,如下面几个:可以看到轴
wght = 400
以及wdth = 100
形成的坐标Regular
,也就是命名实例。Regular
是给特定坐标提供的预设名称,也是该子字体的名称,可以让使用者直接使用。使用可变字体的时候,如果没有指定子字体,其采用默认轴值。(css 里面修改font-variation-settings
,也就是实例了)。对于可变字体,有两个表是必须的:
fvar
和STAT(style attributes)
,后者是样式属性,每个在fvar
里面的每一条轴和子字体都需要在STAT
里面有对应的信息。STAT
用来区分字体族下面的不同的字体,支持动态属性,比如fvar
里面的wght(无极)
,也支持静态属性,比如italic
是否为斜体这些,展示Variable Font
下的样式名称,比如Medium
这样的字体。其他的表则是描述
fvar
里面字体轴变化时字形的变化情况,例如avar
,是非线性的轴变化数据,例如字体的width
轴,若变化区间是100-200
,线性的时候,则150
表示字体的字形宽度是两个极值的正中间,但是非线性变化,就使得值不是均匀的变化的,150
可能不是字形宽度上的正中间状态。这种非线性变化也符合用户习惯。还有gvar
,存储字形在轴上的变化信息,描述glyp
中各个点的变化情况,可以说是非常重要的。字体提取工具
看了上面的
table
介绍,字体的处理,其实就是对table
的处理,fonteditor-core
对可变字体的处理,看了一下源码的结构,well,根本就没有可变字体的表处理,连fvar
的踪迹都没有。于是开启大海捞针的方式,在
github
里面找,最后发现一个opentypejs/opentype.js
仓库,卧槽,难道是官方的嫡系部队?只是打开到结构目录还是很失望,都是三四年前的代码了,和fonteditor-core
差不多,虽然有fvar
表,但是其他可变字体的表一个都没有。抱着试一试的想法,用一下,最后的打包出来的字体,虽然比fonteditor-core
好不少,但是压根就不可变。。。。毕竟连gvar
也没有。最后看到了这个roadMap,里面介绍到:本来决定要放弃了,毕竟官方也不支持系列,但是总觉得有问题,难道可变字体没有工具?都好几年历史了,没有人造轮子吗。。。。
最后找到了字体处理的重量级库
fonttools
,一个python
库,打开一看密密麻麻的的table
处理,有 50 个以上的处理,对比一下fonteditor-core
的 18 个表处理,简直是。。。。。。在fonttools
里面也找到了各种各样的可变字体处理表,比如gvar
,只是对python
不是很熟悉,而且一上来就看源码,有点吃力,所以就放弃了(想起了看 esbuild 源码的经历)。fonttools
里面有很多工具,提取字体用的是pyftsubset
,通过指定文件字符来确定要输出的字形,基本上一顿操作下来,从 22M 的字体包,压缩到 300kb。正常来做这就可以了,但是 原本字体包还包含了斜、高度轴,这些轴,项目用不上,而且wght
也就用到了550-1000
的范围,能不能去掉剩下的部分呢? 这样不就可以完美压缩字体了,甚至wght
就用了550
和1000
两个值,其他的能不能抛弃掉呢?可能这个就要用专业的设计工具了(比如 Adobe Illustrator?),目前在fonttools
没有看到更多可操作空间,如果有大佬晓得一定要告知。一般可变字体的体积是要大于单个字体的(字体族里面的单个字体),只有当需要用到同一字体族的多个字体的时候,可变字体收益才很大。当然如果需要艺术字那就另当别论了。另外
font-variation-settings
是属于比较基础的 API 了,如果要设置字重的话,可以使用font-weight: 550
是不是很熟悉?这个和以前的 CSS 是一致的,只是CSS Fonts Level 4
做了扩展,当然还有其他几个轴的,比如font-stretch
。require
相比于字体,
require
可以说是以前的一个知识盲区。在Vue
里面,如果需要加载资源可以采用require
方式引入,但是时不时的总会遇到无法加载资源的问题。直到一次想要把资源路径作为props
传入组件,再通过require
来获取,结果是获取到图片了,但是还引发了另外一个严重的问题,页面的样式错乱?通过审查打包出现的代码,发现原本完全没有引入的
scss
文件都被打包到样式文件里面,如果去掉require(urlProp)
则一切正常,这个就很神奇了,而且前者的打出的包还很大。有种奇奇怪怪的感觉。后面耐心的看
webpack
文档才晓得:如果采用
require('./template/' + name + '.ejs')
的方式,那template
文件下面的所有ejs
文件都会被引用,形成一个上下文的map
对象,导致该目录下原本不会被使用的文件,也被打包使用上了,这也就是为什么使用了require(urlProp)
会加载上错误的资源,可想而知,若require
里面完全采用传参的方式,会使其无法分析正确的Directory
, 于是从根文件src
开始查询文件。。。。。至于要如何破局呢,
urlProp
为了可扩展性,是要从外部传入的,而里面要读取资源只能用require
了,直到看到了下面的require.context
的方式,表达式如下:通过在外部指定目录,和正则就能获得正确的资源路径,再传给
urlProp
就完美了。上面的
mode
配置呢,其实是和webpackMode
类似的,有sync
、eager
、lazy
、lazy-once
、weak
、async-weak
一共六种。其中sync
是默认的,会直接打包到文件里面,而lazy
则会生成可延迟加载单独的chunk
。这里我用到的是
lazy-once
。为什么呢,因为我需要从require.context
里面引入的资源非常多,肯定是要拆包的,而lazy
虽然是懒加载了,但是所有文件都单独形成chunk
,导致增加了很多文件,lazy-once
就很舒服,将所有文件合成一个chunk
,只需要通过promise
的方式获取正确的路径就可以了,比如urlProp(oneFileName).then(src => list.push(src))
这样的方式。总结
require
部分算是一个小知识点,至于深入的理解,比如Directory
目录的获取和分析,感觉有点类似,可能是@babel/parser
的形式,通过ast
分析表达式来获取目录?后面的理解就没有去研究了,倒是解决了一直以来使用require
的困惑(指不定以前有好多写的不太正常的 bug,采用require
多加载了多余文件。。。。。呵呵呵)。字体部分,更像是一个新领域的探索,想要不断的优化页面,而新版本的字体就是重中之重了,从开始的通过
node.js
来debug
,到最后定位到fonteditor-core
再到fonttools
,可以看到前端的字体轮子还是少了(比如参照fonttools
代码,更新fontedior-core
?)。更多的是学习字体的结构,看各个table
的作用,对字体的展示也有初步的理解,但是没有去研究代码层面的实现,没有深入去,更多的是浅尝辄止,可能兴趣就到这里吧,没有更多的想法了,想要深入探索更多的东西,更有价值的吧。写完的时候又一个台风飞过,今年的台风真是奇怪。
参考
技术文档,当然微软的是 Opentype,苹果的是 TrueType 与 AAT。