使用SF Mono制作最佳编程字体的故事


您使用终端吗? Terminal.app? iTerm2?超?或者是其他东西?

那您用什么字体呢?门洛? Consolas?混蛋?这就是今天的故事。

  • 目标读者

    • 想要在终端和编辑器中使用超酷等宽字体的人。
    • 想要使用FontForge Python使用现有字体的人。

编程字体SF Mono Square

我使用Ricty已有很长时间了,但是我一直非常注重自己制作字体。由于许可证的原因,无法分发创建的产品,但是Homebrew使任何人都可以引入它。

1
2
3
brew tap delphinus/sfmono-square
brew install sfmono-square
# /usr/local/opt/sfmono-square/share/fonts にフォントが作成されます

スクリーンショット 2018-12-1 19.05.15.png

SF Mono Square具有以下功能。

  • 仓库:delphinus /自制-sfmono-square

  • Apple的正版SF Mono用于半角字体,而Migu 1M用于全角字体。

    • 您可以在以下Qiita文章中阅读有关旧金山的更多信息。
    • 探索San Francisco Fonts-Qiita
  • 调整字形,以使半角或全角的宽度正好为1:2。
  • 可视化双字节空间。
  • 书呆子字形(包括所谓的电力线字形)的添加。

  • 许多其他字形调整。

简而言之,它是Ricty的SF Mono版本+α。

  • 显示TypeScript代码的示例
    スクリーンショット 2018-12-16 14.34.58.png

  • 日语很完美
    スクリーンショット 2018-12-16 14.35.48.png

  • 配有从电力线字形和书呆子字体派生的各种图标
    スクリーンショット 2018-12-16 14.45.41.png

SF Mono是最近的macOS和Xcode的标准配置。这是一个有趣的许可证,但是除非您出售或分发字体文件本身,否则以这种方式修改和使用它似乎没有问题(我与Apple联系)。这适用于macOS中包含的所有字体,而不仅限于SF Mono。好胖

下面,我将描述如何决定创建SF Mono Square,然后将解释如何实际创建字体。

编程字体需要什么

使用终端或编辑器时应为字体选择什么?这是我认为重要的清单(至少对我而言)。

1.必须为等宽字体

如果您问10个人,则10个人会同意。用比例字体编码是不可能的。

2.字体必须适合在CLI环境或IDE

中使用。

"工作"包括浏览日志和文档以及编码。最重要的是,这些字符应易于阅读,并且不应与其他字符混淆。具体而言,以下几点至关重要。

  • 一眼就能分辨出0oo1iil

  • 适当的字符间距和行间距。

スクリーンショット 2018-12-16 14.52.51.png

对许多人来说,这些应该是强制性的。其他一些"编程"字体具有以下优点:

3.支持双字节字符

有终端应用程序,例如

iTerm2,可以为半角和全角指定不同的字体,但是仍有许多应用程序,例如Terminal.app,仅允许您指定一种字体。如果您想使用这样的应用程序,或者只是想拥有统一的外观,则在一种字体中包含半角或全角字形会很好。

M FONTS的M系列,Ricty,Chintz Gothic等都很有名。特别是Ricty是一种具有主导世界的各种独特功能(稍后描述)的字体。

  • Ricty中的文本示例。
    スクリーンショット 2018-12-16 15.33.21.png

4.支持连字(连字)

这可能与2.易于阅读背道而驰,但是它之所以受欢迎,是因为它比CLI环境中的表达更具表达力。

费拉密码,Hasklig,Iosevka等很有名。上述Sarasa Gothic是从Iosevka派生的一种字体,因此它支持连字(可能是唯一的日语字体)。

  • Sarasa Gothic中的文本示例。
    スクリーンショット 2018-12-16 15.28.23.png

但是,这次创建的SF Mono Square不支持这种连字。有时在技术上很难制造,但就个人而言并不是那么重要...我想下次尝试。

5.支持各种外部字符

在以前不经常使用连字的年代,对于那些不愿使用CLI(主要是Vim)表现力的人来说,Powerline是一场革命。我使用了一个补丁程序为字体添加了新的外部字符(字形),以便获得很酷的状态行并在Vim和各种shell中进行提示。

这个想法也影响了那些制作新程序字体的人。在那之后,吸引人的是,一种又一种的字体可用于电力线。即使现在Powerline本身已经放慢了速度,包含所谓的" Powerline标志符号"的字体也证明它们是用于编程的。

  • 当您要使状态行看起来很酷时,这是必不可少的。
    スクリーンショット 2018-12-16 15.12.16.png

另外,Nerd Fonts是一个使这个想法更进一步的项目。从站点上可以看到,我们已经发布了一个补丁,该补丁将用于Web应用程序的UI图标的字形(例如Font Awesome和Octicons)放置在字体的外部字符区域中。如果您走的很远,则很容易在2.中看到,并且我认为它并没有造成混淆,因此最好不要过分依赖它。

スクリーンショット 2018-12-16 15.04.58.png

Ricty怎么了

好吧,这是里奇(Ricty)来了几次。可以说这是日语环境中编程字体的确定版本,即使发布了很长时间也是如此。可以更快地查看官方网站的功能,但是其中有一些功能。

  • 使用Ricty的文字范例
    スクリーンショット 2018-12-16 15.33.21.png

  • 流行的Inconsolata用于半角字体。
  • 源自Migu 1M的易于阅读的双字节字符。特别是,易于区分浊音标记和半浊音标记的假名字符是独特的。
  • 可视化双字节空间。

    • 许多编辑器功能都可以执行此操作,但是现在只需安装字体即可实现。
  • 其他各种。

好吧,我对此感兴趣的是Inconsolata粗体字。当我使用Ricty在终端中编写代码时,感觉宽度很小。 SF Mono没有那个。

  • Ricty 16pt
    スクリーンショット 2018-12-16 15.47.44.png

  • SF Mono 14pt
    スクリーンショット 2018-12-16 15.50.36.png

这是从哪里来的?字体的纵横比是不同的

检查FontForge中的字体详细信息

FontForge是一个开源字体编辑软件。与Mac / Windows / Linux兼容。对于Mac,您可以从官方网站下载它,也可以使用Homebrew安装它。

1
2
brew install fontforge
brew cask install fontforge

在Finder中双击

/Application/FontForge.app以启动GUI。在这里,由于篇幅所限,详细用法将被折叠。

现在,我们在FontForge中打开Inconsolata和SF Mono。您可以从以下获得每个。

  • Inconsolata→Google字体

  • SF Mono→字体--Apple Developer

<表格>

因科拉塔

SF Mono


<身体>

スクリーンショット 2018-12-16 15.57.08.png

スクリーンショット 2018-12-16 15.57.12.png


我刚刚打开了相同的" A"字形。如果查看此内容,可以看到围绕该字形的矩形在Inconsolata中垂直拉长。长宽比的计算如下。

<表格>

垂直

水平

长宽比


<身体>

因科索拉塔

1000

500

2.0

SF Mono

2048

1266

1.617694


  • 在此,"垂直"使用"上升下降"的值。单击此处以获取术语的详细说明。

SF Mono纵横比,您在某处看到过吗?所以。黄金比例!这是SF Mono字形看起来很漂亮的秘密。

日语字体长宽比

这里的问题是日语字体。可以自由确定字母字符的长宽比,但是假名和假名在日语中基本上是正方形。您必须管理纵横比才能将其与SF Mono结合使用。现在,我们比较一下SF Mono和Migu 1M度量标准(确定字体格式的数字)。

<表格>

上升

下降

宽度


<身体>

SF Mono

1638

410

1266

Migu 1M

860

140

1000


<表格>

SF Mono

Migu 1M


<身体>

SF Mono Ascent-Descent.png

Migu 1M Ascent-Descent.png


如果并排查看,则字形除了长宽比之外,排列方式也有所不同。具体来说,Migu 1M的Ascent范围更广,因此通常低于SF Mono。换句话说,有必要在保持字体的纵横比均匀的同时上下移动字体一点。

制作字体

还有许多其他要考虑的问题,但首先让我们实际创建一种字体。

FontForge允许您在GUI中自由调整字体,但是如果要对所有字形都进行同样的调整,则使用脚本更容易。 FontForge可以使用Python自动化,因此可以使用它。

简单来说,如果要组合两种字体中的一种,则可以编写如下。

1
2
3
4
5
6
import fontforge

font = fontforge.font()
font.mergeFonts('SFMono-Regular.otf')
font.mergeFonts('migu-1m-regular.ttf')
font.generate('merged-font.ttf')

很简单!但是,这不允许您调整字形的大小和位置,并且不包括字体名称或其他信息。要进行这些设置,需要进行大量的调整。让我们跟随下面的实际脚本。

SF Mono Square的详细说明

下面的存储库中总结了用于创建

字体的脚本(也可以用作自制水龙头)。让我们阅读每个脚本。

  • 仓库:delphinus /自制-sfmono-square

自制公式-sfmono-square.rb

从此处单击↑之类的文件名,以打开GitHub的链接。

这是一个Homebrew配置文件。安装必要的应用程序(FontForge,Python等)后,我运行一个名为bin/sfmono-square的shell脚本。编写Homebrew公式超出了本文的范围,将被省略。

  • 酿造/ Formula-Cookbook.md掌握·自制/酿造

截至2020年7月,Homebrew的fontforge公式为[email protected]编译了库。请注意,它不能与OS标准的Python或python公式一起使用。

为解决此问题,sfmono-square.rb动态添加路径并在下一节中执行build.py

1
2
3
4
5
6
7
8
9
10
11
12
# Set path for fontforge library to use it in Python
fontforge_lib = Formulary.factory("fontforge").lib / "python3.8/site-packages"
# Supply the full path for Python3.8 executable to use with fontforge
python38 = Formulary.factory("[email protected]").bin / "python3"

system python38, "-c", <<~PYTHON
  import sys
  sys.path.append('#{buildpath / 'src'}')
  sys.path.append('#{fontforge_lib}')
  import build
  sys.exit(build.build('#{version}'))
PYTHON

构建脚本主体– build.py

该脚本为每个阶段调用其他脚本。那时,当播放多个字体文件时,使用concurrent.futures模块是因为它可以并行工作并完全使用CPU。

Migu 1M调整-migu1m.py

该脚本对Migu 1M字体进行了各种调整。

1.上升/下降(字体高度)调整

字体的高度由两个属性ascent / descent将这两个值的比率设置为与SF Mono相同的比率后,最后将该值设置为em属性,字形将自动缩放和调整。

1
2
3
4
5
6
7
8
ASCENT = 1638
DESCENT = 410
OLD_EM = 1000
EM = ASCENT + DESCENT

font.ascent = float(ASCENT) / EM * OLD_EM
font.descent = float(DESCENT) / EM * OLD_EM
font.em = EM

2.缩小字形

如果您附加纵横比为黄金比例的SF Mono和适合正方形的字形Migu 1M,则Migu 1M的字形会相对较大。需要提前减少,但是这个百分比已经是直觉和经验了。这次我使用的是82%的值,但我别无选择,只能反复尝试查找看起来不错的值。

您可以通过为glyph.transform()方法提供表示您要转换的内容的矩阵来转换

字形。手工编写矩阵很困难,因此让我们使用psMat模块的便捷功能。

1
2
3
4
5
6
7
8
9
10
# 0.82 倍に縮小するための行列
scale = psMat.scale(0.82)
# scale() だけだと左に寄りすぎてしまうので、translate() で右にずらす
# EM は横幅全体を表す値(今回の場合は 2048)
x = EM * (1 - 0.82) / 2
trans = psMat.translate(x, 0)
# 2つの行列の積を求める
mat = psMat.compose(scale, trans)
# 変換実行!
glyph.transform(mat)

这里最棘手的部分是赋予translate()方法的x的值。对于半角字符,此值是全角字符的一半。我没弄明白稍后会被SF Mono覆盖的半角字符,在这里我只用translate()将半角假名向右移动。

3.可视化双字节空间

双字节空间的可视化(这是Ricty的主要功能)也在SF Mono Square中实现。这是受Ricty的生成脚本启发的。

1
2
3
4
5
6
7
8
9
10
11
12
13
font.selection.none()
# ?  をコピーして 0x3000(全角空白のコードポイント)に貼り付け
font.selection.select(0x2610)
font.copy()
font.selection.select(0x3000)
font.paste()
# ?  をコピーして 0x3000 に貼り付け
font.selection.select(0x271a)
font.copy()
font.selection.select(0x3000)
font.pasteInto()
# intersect() メソッドで、重なり合った部分のみを残してそれ以外を削除
font.intersect()

making zenkaku space.png

您认为这很好。

4.斜体

进行所有修改后,最后创建斜体字体。 Migu 1M字体仅在常规和粗体中可用。创建倾斜和粗体倾斜以匹配SF Mono。

skew()方法非常适合此目的。这次,给出值0.2以生成矩阵。

1
2
3
mat = psMat.skew(0.2)
font.selection.all()
font.transform(mat)

スクリーンショット 2018-12-16 16.33.25.png

感觉很好。

SF单声道调整-sfmono.py

接下来,我们将修改SF Mono本身。

1.补充斜体字形

SF Mono具有一些字形,例如框形文字和删除了斜体和粗体斜体的箭头。要弥补这一点,分别从常规和粗体复制。

1
2
font = fontforge.open('SFMono-RegularItalic.otf')
font.mergeFonts('SFMono-Regular.otf')

顺便说一句,当搜索此类信息时,称为pettarin / glyphIgo的工具很有用。将显示包含字体的字形列表。

  • 顺便说一句,该工具不适用于Python3,因此我将发布通过分叉修复的工具。

    • 三角洲/雕文
1
2
# フォントファイルの含むグリフ一覧を標準出力に吐き出す
glyphIgo.py list -f SFMono-Regular.otf

2.将长宽比设置为1:2

好吧,这是肝脏。最初,我为此制作了这种字体。

为了将SF Mono与Migu 1M结合使用,必须调整纵横比,使其恰好是半角。

<表格>

垂直

水平

长宽比


<身体>

Migu 1M

1000

1000

1.0

SF Mono

2048

1266

1.617694

SF Mono(调整后)

2048

1024

2.0


1
2
3
4
5
6
7
OLD_WIDTH = 1266
WIDTH = 1024
SCALE_DOWN = float(WIDTH) / OLD_WIDTH

mat = psMat.scale(SCALE_DOWN)
glyph.transform(mat)
glyph.width = WIDTH

<表格>

转换前

转换后


<身体>

スクリーンショット 2018-12-16 15.57.12.png

スクリーンショット 2018-12-16 16.38.59.png


较小,宽度较窄。而已。

SF Mono和Migu 1M-sfmono_square.py的合成

最后,附加SF Mono和Migu 1M。在那之后,这是对愤怒的波浪的微调。

1.设置字体信息

在附加字体之前,请准备一个空字体并为其设置字体信息。

"字体信息"不仅是字体名称和许可证。有证据表明,由于历史原因已将信息添加到Ad Hoc中,因此我只是搜索通过反复试验设置的值。

  • 在FontForge的GUI中打开现有字体,以查看可以设置的值。
  • 搜寻该值以找出含义。

    • 无论如何,几乎没有信息...我用我想知道的短语搜索了Microsoft的OpenType规范。
  • 用脚本设置。

    • FontForge的Python API的文档非常糟糕。我依靠davidhalter / jedi-vim代码完成来查找属性。
  • 这里没有显示详细的代码,因为设置它的唯一方法是以简单的方式将值设置为属性。看一下实际的代码。

    1
    2
    3
    4
    5
    6
    7
    # 空フォントを作る
    font = fontforge.font()
    # ひたすら値を設定しまくる
    font.ascent = ASCENT
    font.descent = DESCENT

    ...

    2.连接SF Mono和Migu 1M

    只需将其合并为

    1中创建的空字体。这是一个瞬间的杀戮。

    1
    2
    font.mergeFonts('modified-migu1m-regular.ttf')
    font.mergeFonts('modified-SFMono-Regular.otf')

    3.将来自Migu 1M的双字节字符替换为SF Mono字形

    此处的"双字节字符"是Unicode块中所谓的半角或全角字符。所谓的全角数字或全角字母。从代码点U FF01开始有95个字符。

    Migu 1M单独使用时不会感到奇怪,但是与SF Mono结合使用时,它看起来有点轻。这可以通过复制SF Mono的半角字符来解决。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    hankaku_start = 0x21
    zenkaku_start = 0xff01
    glyphs_num = 95
    font.selection.none()
    for i in range(0, glyphs_num):
        # 半角文字をコピー
        font.selection.select(i + hankaku_start)
        font.copy()
        # 全角文字に貼り付け
        font.selection.select(i + zenkaku_start)
        font.paste()
        font.selection.none()

    如果仅复制它,则字体将保持在左侧附近。稍微向右移动。

    1
    2
    3
    # 毎度おなじみ translate()
    trans = translate(WIDTH / 4, 0)
    glyph.transform(trans)

    这还没有结束。 Half-width?全角形状包含括号,例如()「」。可以将左撑杆向左移动,而将右撑杆比往常更向右移动。

    <表格>

    开括号

    右括号


    <身体>

    move-to-right.png

    move-to-left.png


    此外,某些异常括号??仅使用半角或全角,没有半角版本。它们按原样使用了源自Migu 1M的字形。这样我真的很麻烦...

    • 变形前
      スクリーンショット 2018-12-16 16.59.08.png

    • 改造后
      スクリーンショット 2018-12-16 16.59.12.png

    终于(在这里)完成了。

    4.使一些符号变为半角

    最近,由于Unicode的普及,许多CLI工具使用了各种符号。例如,jonas / tig使用许多符号来表示漂亮的提交树。

    スクリーンショット 2018-12-16 17.03.01.png

    在此类CLI工具中使用的许多符号(例如●?◎)在SF Mono中找不到。我别无选择,只能使用Migu 1M,但它们当然是双字节字符。许多CLI工具期望这些符号(更具体地说是东亚宽度)为A字体的半角。同样,我也决定使用这种字体以半角字符表示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 以下のグリフは半角にする
    HANKAKU_GLYPHS = [
        0x25a0,  #   BLACK SQUARE
        0x25a1,  # □  WHITE SQUARE
        0x25cb,  # ○  WHITE CIRCLE
        0x25cc,  # ?  DOTTED CIRCLE
        0x25ce,  # ◎  BULLSEYE
        0x25cf,  # ●  BLACK CIRCLE
        0x25ef,  # ?  LARGE CIRCLE
    ]

    现在,如何将全角字形转换为半角字形,以免感到奇怪?但是没有简单的方法可以做到这一点。每次只能盯着实际字形来选择最佳的变换矩阵。

    在这里,多次出现的psMat.scale()行为成为问题。用这种方法进行的缩放有点不直观。

    1
    2
    3
    # (例)グリフを 0.8 倍にする
    mat = psMat.scale(0.8)
    glyph.transform(mat)

    即使您说"乘以0.8",也无法指定将中心点缩小的位置,并且文档中也没有关于中心点的描述。实际尝试时,我发现中心点位于(0,DESCENT)。

    center-for-scale.png

    我们还发现Migu 1M符号与左下角(DESCENT,DESCENT)和右上角(WIDTH --DESCENT,WIDTH --DESCENT)配合成正方形。基于此,您可以通过执行以下操作来生成半角字形。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    WIDTH = 2048
    DESCENT = 410
    SCALE_DOWN = 0.65  # 縮小比率。この比率は何度か試して決めました。

    orig_glyph_width = WIDTH - DESCENT * 2  # 元々のグリフの幅、および、高さ
    glyph_width = float(orig_glyph_width) * SCALE_DOWN  # 変換後のグリフの幅、および、高さ
    trans_x = (WIDTH / 2 - glyph_width) / 2  # 縮小後に右にずらす
    trans_y = (WIDTH - glyph_width) / 2 - DESCENT  # 縮小後に上にずらす
    trans = psMat.translate(trans_x, trans_y)
    origin = psMat.translate(-DESCENT, 0)  # グリフを中心点に合わせる行列
    scl = psMat.scale(SCALE_DOWN)  # グリフを縮小する行列
    mat = psMat.compose(psMat.compose(origin, scl), trans)
    glyph.transform(mat)

    <表格>

    转换前

    转换后


    <身体>

    スクリーンショット 2018-12-16 17.19.26.png

    スクリーンショット 2018-12-16 17.19.29.png


    不,很难。我数学不好,所以很累。

    书呆子字体标志符号合成-font_patcher.py

    最后,通过Nerd Fonts合成字形。最初,我为此使用了官方脚本字体修补程序,但是我完全重写了它以进行一些修改。具体来说,它在以下几点上不同于原始的书呆子字体。

    1.更改了Octicons的代码点,材质图标

    <表格>

    书呆子字体

    SF Mono Square


    <身体>

    Octicon

    U F400 .. U F505

    U F400 .. U F4e9

    材质图标

    U F500 .. U FD46

    U E800 .. U EFFF,U F500 .. U F546


    进行此更改的原因是,Nerd Fonts使用的区域是可用于此类外部字符的专用区域(U E000 .. U),因为它与F8FF不同。私有使用区域之后立即以U F900开头的CJK兼容汉字包括通常用于专有名词(例如"﨑(U FA11)"和" Fuku(U FA1B)")等专有名词的汉字。这是个问题。

    • 我们已与总公司就此事提出了一个问题。预计将在下一个主要版本升级中解决。

      • [建议]修复某些字形的无效代码点·问题#365·黑柳病/书呆子字体

    2.调整每个字形的大小

    书呆子字体包括除Octicon和Material图标之外的各种图标集,但是由于它们是单独开发的,因此字形的大小会有所不同。通过对每一个进行较小的修改,我对其进行了调整,以使它们看起来尽可能相似。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if info['name'] == 'Seti-UI + Custom':
        # この例では縦横それぞれ 1.1 倍に拡大した上、
        # 左に 100、下に 450 動かしています。
        x_ratio = 1.1
        y_ratio = 1.1
        x_diff = -100
        y_diff = -450

    elif info['name'] == 'Devicons':
        # 以下延々と続く
        ...

    如上所述,我已进行了一些小的更正。请参阅实际代码以获取完整列表。

    apngb-animated.png

    这是校正前后的比较...以这种方式进行比较时非常微妙。但是我对此很担心!

    最后

    我们已经了解了有关创建字体的更多信息。我所做的只是从现有字体进行合成,但这使得调整细节非常困难。那些从头开始创建字体的人真的很沮丧。

    网上很少有关于使用FontForge修改字体的信息(尤其是日语)。希望本文能为您带来字体爱好者的舒适生活。