落霞孤鹜文楷,但我总觉得中英文混排时它的英文字型和中文实在不太搭配。楷体本就处在一个有衬线特征但不是很多的模糊地带,放到同类型的英文书法字体里混排就更加诡异,于是在最开始选择了 Palatino 作为英文字体。
这个选择仍然很别扭,点开之前的版本1,可以看到 FM 那篇文章标题的英文衬线要素过多,
明明两个都是好看的字体,为什么放在一起就变成这样了呢?
后来某一天醒悟了,简体文楷的衬线特征并不是特别明显,于是我把搜索范围扩大到非衬线体,找到了一款吸收了 Garamond 精气神的字体 Ysabeau,和落霞孤鹜文楷整体都很搭配,唯一的缺点就是...
它也和 Garamond 一样数字是不齐线的2,"012" 和小写 "x" 一样高,"34579" 则有降部,而 "68" 有升部。单独放在纯英文的上下文里当然没有什么问题,和中文混排的时候就显得有些突兀了。
当然这难不倒 精通 CSS 3的我,这个字体其实是提供了替换字形的,通过 CSS 设置 font-variant: lining-nums
即可强制使用齐线数字,让所有数字和大写字母一样高。
但是这跟 OG Image 有什么关系呢?
前面提到我准备用跟网页一样的字体来渲染 OG Image,这样也会碰到 oldstyle figure 的问题。网页上通过 CSS 改了,而 CanvasKit 是 skia 的 WASM 封装,它的文档不能说是没有只能说是几乎没有,我只能从 TS 的类型定义里猜
在 CanvasKit 的 TextStyle
上有两个长得像 CSS font-variant
的属性:
export interface TextStyle {
backgroundColor?: InputColor
color?: InputColor
decoration?: number
decorationColor?: InputColor
decorationThickness?: number
decorationStyle?: DecorationStyle
fontFamilies?: string[]
fontFeatures?: TextFontFeatures[]
fontSize?: number
fontStyle?: FontStyle
fontVariations?: TextFontVariations[]
foregroundColor?: InputColor
heightMultiplier?: number
halfLeading?: boolean
letterSpacing?: number
locale?: string
shadows?: TextShadow[]
textBaseline?: TextBaseline
wordSpacing?: number
}
export interface TextFontFeatures {
name: string
value: number
}
export interface TextFontVariations {
axis: string
value: number
}
export interface TextStyle {
backgroundColor?: InputColor
color?: InputColor
decoration?: number
decorationColor?: InputColor
decorationThickness?: number
decorationStyle?: DecorationStyle
fontFamilies?: string[]
fontFeatures?: TextFontFeatures[]
fontSize?: number
fontStyle?: FontStyle
fontVariations?: TextFontVariations[]
foregroundColor?: InputColor
heightMultiplier?: number
halfLeading?: boolean
letterSpacing?: number
locale?: string
shadows?: TextShadow[]
textBaseline?: TextBaseline
wordSpacing?: number
}
export interface TextFontFeatures {
name: string
value: number
}
export interface TextFontVariations {
axis: string
value: number
}
此 TextFontVariations
非彼 font-variant
,应该是 Variable Font 的配置,那么剩下的就是 TextFontFeatures
了。但是 name 和 value 应该传什么值,skia 的文档里没有注释,于是我只能在 skia 的仓库里挖呀挖呀挖
一个 SkFontFeature
是一个 SkFourByteTag
:
SkShaper::Feature feature = {
SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
SkToU32(ff.fValue),
block.fRange.start,
block.fRange.end
};
SkShaper::Feature feature = {
SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
SkToU32(ff.fValue),
block.fRange.start,
block.fRange.end
};
但并没有地方解释这个 SkFourByteTag
是什么,并且 skia 项目里面其他地方也在用,在源码里搜索到的用法就有很多种,但唯独没有找到 lining-num
对应的四个字节,我隐约感觉 tag 的定义似乎不在 skia 这边。
不过我搜到了一个 flutter 的 issue,其中提到了一个叫 tnum
的 font feature。我知道这个 feature 应该全名 tabular number,对应 CSS 的 font-variant: tabular-nums
4,那么 lining-nums
很可能对应 lnum
这个 feature,我把这个值填到代码里
它 work 了,但我的脑子不 work 了
晚上回到家我请来 ChatGPT,问祂有哪些 SkFourByteTag
组合:
Sure! Here are some common
SkFourByteTag
values that are used to control font features:
aalt
: Access All AlternatesThis feature, when present in a font, replaces the default glyphs with alternate forms that provide better joining behavior. It is often used in scripts such as Arabic, where letterforms must be joined together in different ways depending on their context.
calt
: Contextual AlternatesThis feature, when present in a font, replaces certain character combinations with alternate glyphs that improve the overall appearance of the text. It is often used in scripts such as Arabic or Devanagari, where certain letter combinations require special handling.
liga
: Standard LigaturesThis feature, when present in a font, replaces certain letter combinations with ligature glyphs that improve the overall appearance of the text. It is often used in scripts such as Latin or Cyrillic, where certain letter combinations can be difficult to read in their default form.
dlig
: Discretionary LigaturesThis feature, when present in a font, replaces certain letter combinations with ligature glyphs that are more decorative in nature. It is often used in display or decorative fonts.
smcp
: Small CapitalsThis feature, when present in a font, replaces lowercase characters with smaller versions of the corresponding uppercase characters. It is often used in titles or headings to add emphasis.
frac
: FractionsThis feature, when present in a font, replaces certain character combinations with glyphs that represent fractions. It is often used in typesetting mathematical or scientific text.
These are just a few examples of the many
SkFourByteTag
values that are used to control font features. Different fonts may support different sets of features, so it's important to consult the font's documentation to determine which features are available.
于是我又问祂,你知道 tnum 和 lnum 吗?
The
tnum
andlnum
tags inSkFourByteTag
refer to different types of numerals in a font.
tnum
stands for "tabular figures" andlnum
stands for "lining figures". These are two different styles of numerals that are often included in a font.Tabular figures are designed to be the same width, so that they can be aligned in columns. They are often used in tables, charts, and other situations where numerical data needs to be presented in a structured format.
Lining figures, on the other hand, are designed to be the same height as the uppercase letters in the font. They are often used in running text, where they help to maintain a consistent visual rhythm.
By using the
tnum
andlnum
tags in Skia, you can specify which style of numerals should be used for a particular block of text. This can be useful in situations where you need to present numerical data in a consistent and easily readable format.For example, to use tabular figures in Skia, you could set the paint's text encoding mode to
kGlyphID
and then apply thetnum
tag to the text:SkPaint paint; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTypeface(myTypeface); paint.setFourByteTag(SkFourByteTag('t', 'n', 'u', 'm')); canvas->drawString("1234567890", x, y, paint);
SkPaint paint; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTypeface(myTypeface); paint.setFourByteTag(SkFourByteTag('t', 'n', 'u', 'm')); canvas->drawString("1234567890", x, y, paint);
This would cause Skia to use the tabular figures from the specified typeface for the text "1234567890". To use lining figures, you would set the tag to
lnum
instead.
不愧是博物之神,尽管文档和源码里都没提到这些 tag,祂仍然给出了正确的答案。这些四字标签一定来自某个比 skia 更底层的地方,只是我还不知道在哪里。
我本来准备先咕到这里,但是在给这文章贴链接的时候突然注意到 MDN 在 font-variant-numeric 的文档里提到了:
lining-nums activating the set of figures where numbers are all lying on the baseline. It corresponds to the OpenType values
lnum
.
得来全不费功夫,真相一下就浮出了浮出水面。
FontFeature 是 OpenType 标准里定义的字体特性,由于是字体渲染的参数所以 skia 并不会直接处理这些值。
可用的 FontFeature 会注册到 OpenType Layout Tag Registry 中,但私有的 FontFeature 恐怕也是存在的,所以 CanvasKit 的 TS 定义上也没有枚举 FontFeature 可能出现的值(
我在 MDN 上也找到了一篇介绍 Font Feature Settings (zh-CN) 的文章,详细介绍了许多常用的 font feature。
啊,没用的知识又增加了。