OpenType 特性(三):高级上下文变体

教程
作者:Rainer Erich Scheichelbauer
en fr zh

3 三月 2018 发布日期: 9 八月 2012

在设计一款手写字体,每个字母都有大量变体?读一下这篇教程吧。希望你喜欢瑞典流行乐。

当你使用一款模仿手写风格字体打出一对相同的字母时,比如 eel 或 better,相邻的两个字符形一模一样——没有什么比这更无聊了。为什么不让你的字体能够在每个字符形的多种变体间循环呢?

字符形变体

现在,首先我们需要设置变体字符形。将变体编入不同的 “风格变体集”( stylistic sets)是个不错的主意。所以,假设你有字符形 A,你就会添加 A.ss01A.ss02。同理,为字符形 B 添加 B.ss01B.ss02。对你想要循环的每一个字符形,甚至可能是对你所有的字符形都这样做。如果你想,你甚至可以一直做到 .ss20。为了简单起见,我为每个字符形保留两种变体。A 和 B 的变体可以如图所示:

左侧的 AB 是默认字符形,后面的两组变体为 .ss01.ss02 字符形。

显然,我们需要使用上下文替代字来实现在替换字符形之间的循环。我们不能强求用户分别选择每个字母,在字形面板中挑选替代字符形。

上下文循环

我们从一种简单的设置开始,只循环一个字母,比如 A。点击 “字体信息”(Cmd-I)中的 “特性” 标签,添加 calt 特性并如下输入:

sub A A' by A.ss01;
sub A.ss01 A' by A.ss02;

完成后,特性面板将如下图所示:

现在,点击左下角的 “编译” 按钮。然后回到主窗口,新建编辑选项卡(Cmd-T),在左下角的菜单中启用 calt ,然后输入几个 A。耶,有效果了:

所以,这个特性做了什么?第一行中,寻找两个相连的 A 字符形。如果找到了这样一对,就会把第二个 A 替换为 A.ss01。现在,如果我们输入第三个 A,那么又会是一个默认的 A,产生一个 A.ss01 A 组合。哈,懂了,这就是第二行所起作用的地方:寻找在 A.ss01 后面的 A,把它替换成 A.ss02。很酷。

在字符形类别间循环

现在,如果我们不只可以在单个字符形间循环,而是通用于整个字母表,这会不会很酷?不用担心,我亲爱的老朋友,实际上,这就是要用到“字符形类”的地方了。回到 “文件 > 字体信息 > 特性”,点击左下角的加号按钮,在弹出的菜单中选择 “类”,来依次添加所需要的类……

……然后将 xxxx 换成类名称,并添加代码(由空格分开的字符形名称)。对于三个类都这样做:

  • DEFAULT,在这里写下默认字符形,比如 A B C D E F,等等
  • ALT1 在这里列出字体中包含后缀 .ss01 的字符形:A.ss01 B.ss01 C.ss01,等等
  • ALT2 在这里列出字体中包含后缀 .ss02 的字符形:A.ss02 B.ss02 C.ss02,等等

现在,我们的类如图:

当然了,你可以按照需要添加任意数量的字符形名称,只要你的字体里确实有这些字符形,并且在三个类别里保持一致。这意味着,三个类中的字符形数量必须相同、顺序必须一致。否则,你的 A 就可能变成 B,诸如此类。你肯定不想这样。

现在,我们就可以像这样地调整 calt 特性:

sub @DEFAULT @DEFAULT' by @ALT1;
sub @ALT1 @DEFAULT' by @ALT2;

请记住,特性代码中的 @ 表示这是一个字符形类。我们所做的事情实际上和之前完全一样,只是现在不是对于单个字符形,而是作用于整个类。完成后,你的 “特性” 面板将如下所示:

点击 “编译” 按钮,然后铛铛铛铛~看看它是否能正常工作。在 “编辑” 选项卡中,确保 calt 特性已启用,输入“BABABA”,或者用类中出现的字母排出任何你能想到的内容:

成功,击掌!

两组独立循环,加强随机感

我们完成了吗?不,当然没有。我们还有一个严重的问题。我们的循环是按照 1-2-3-1-2-3-… 的规则进行的,对于每个字母都是如此。那好,现在假设我们要写出我们最喜欢的乐队的名字:

直到第二个 B 为止,一切都正常。第一个 A 来自 DEFAULT 组,第一个 B 来自 ALT1,第二个 B 来自 ALT2。这时候问题就来了。第二个 A 又是从 DEFAULT 来的了。所以,虽然我们的循环对于连续的字母运行正常,但当两个字母的距离是循环长度的倍数时,就会出问题。本例中,我们在一个字母的三种版本间循环,这就意味着每三个字母就会来自同一类。该死。

所幸我们可以设置两个不同的循环。一种合理的分法是,辅音字母设为一个循环,元音字母设为另一个。在下文的示例代码中,我会假定元音字母最多两个相连、辅音字母最多三个相连。(事实上,对于大多数语言而言,你的设定都需要比这个更多。但是读过本文后,你就应该可以根据自己的需要来扩展这些设定了。)

那么,我们像这样设置辅音字母的类别:

Con0: B C D
Con1: B.ss01 C.ss01 D.ss01
Con2: B.ss02 C.ss02 D.ss02

Voc0: A E I
Voc1: A.ss01 E.ss01 I.ss01
Voc2: A.ss02 E.ss02 I.ss02

Etc: space comma

同样,根据你的需求来扩展更多的代码。Etc 类别用于放置空格,以及所有你懒得画替代字符形的字母。显然,你可以按任何一种旧有方式来将字符形们划分到这两个类别。你甚至可以将一个(最好是罕用的)字符形同时加入到两个类别中。至于标点符号和数字之类的东西放在哪里,完全取决于你自己。对于某些字母,比如 y 或 w,你可能要考虑一下——它们在一些语言中被认为是辅音字母,而在另一些语言中被认为是元音字母。还有,不要忘记带变音符的字母。

那么,就这样做。我们从辅音字母开始。我们来重写一下之前完成过的内容:

# 辅音字母接辅音字母
sub @Con0 @Con0' by @Con1;
sub @Con1 @Con0' by @Con2;

没什么新东西。不管在哪里,只要两个辅音相连,它们就在三种变体间循环。但是如果它们被其他字母打断该怎么办呢?

我们需要添加下面这一段。和上面一样,不过试图考虑到 “当中杀出” 的字母:

# 辅音字母,接其他,再接辅音字母
sub @Con0 [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con1;
sub @Con1 [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con2;

使用方括号,你可以 “临时” 创建一个类,即不需要事先定义。本例中,我们将所有元音字母类别 VocX 和其他类 Etc 合而为一。看上去相当直观。对于中间隔了两个字母的情况,我们添加:

# 辅音字母,接两个其他,再接辅音字母
sub @Con0 [@Voc0 @Voc1 @Voc2 @Etc] [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con1;
sub @Con1 [@Voc0 @Voc1 @Voc2 @Etc] [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con2;

好了,我们要对元音字母做同样的事情。长话短说,元音字母的代码这样写:

# 元音字母接元音字母
sub @Voc0 @Voc0' by @Voc1;
sub @Voc1 @Voc0' by @Voc2;

# 元音字母,接其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

# 元音字母,接两个其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

# 元音字母,接三个其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

如你所见,我在这里多添加了一段 “接三个其他” 的内容,这是因为在上文中我作了 “元音之间最多有三个辅音” 这样的设定。这样一来,如何继续扩展这个结构也就很清楚了。如果你想让你的替换规则扩展到 “允许间隔更多字母” 的话,只需要添加更多段落,其中包含更多的方括号类即可。如果你的字母表中还有一套替换字符形,那么每段中就要再加一行,用于替换相应的第三个类。

好了,那我们总结一下。到目前为止,我们有这些内容:

# 辅音字母接辅音字母
sub @Con0 @Con0' by @Con1;
sub @Con1 @Con0' by @Con2;

# 辅音字母,接其他,再接辅音字母
sub @Con0 [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con1;
sub @Con1 [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con2;

# 辅音字母,接两个其他,再接辅音字母
sub @Con0 [@Voc0 @Voc1 @Voc2 @Etc] [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con1;
sub @Con1 [@Voc0 @Voc1 @Voc2 @Etc] [@Voc0 @Voc1 @Voc2 @Etc] @Con0' by @Con2;

# 元音字母接元音字母
sub @Voc0 @Voc0' by @Voc1;
sub @Voc1 @Voc0' by @Voc2;

# 元音字母,接其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

# 元音字母,接两个其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

# 元音字母,接三个其他,再接元音字母
sub @Voc0 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc1;
sub @Voc1 [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] [@Con0 @Con1 @Con2 @Etc] @Voc0' by @Voc2;

我们再来测试一次。点击 “编译”,打开一个编辑选项卡,激活 calt 特性然后这样:

哦耶!看到了吗,第二个 A 和第一个就不一样了。你会想唱:“You are the dancing queen, young and sweet, only seventeen!” 感谢聆听,感谢亲爱的 Agnetha、Björn、Benny 和 Anni-Frid(瑞典乐队 ABBA 的四位成员),感谢你们带来的音乐。

现在,如果你真的很极客,就可以创建出三组独立的循环。不过你得自己做一下,本篇教程就是这些了。


2016-02-16 更新:更新字符形类,以便更易理解(感谢 Stephen Nixon!)
2018-03-03 更新:新增关于如何添加 OpenType 类的备注。

Chinese translation by Willie Liu (刘育黎) from 3type (三言).