🍚

Iosevkaをカスタマイズして至高のプログラミングフォントをつくる

2023/08/25に公開

なぜテーマを自作するヲタクは多いのに、フォントにこだわる人は少ないのか。

Iosevkaについて

Iosevkaはオープンソースで開発されているプログラマブルなフォントです。

https://typeof.net/Iosevka/
https://github.com/be5invis/Iosevka

特徴としては、

  • Normal/Term/Fixed(全角文字やリガチャの扱い方)、Sans/Slab、Etoile/Aile(プロポーショナルフォント)、などの様々なスタイル
  • 文字ごとに用意されたvariantや、リガチャごとのon/offを自分でカスタマイズしてビルドできる。
  • 一般的な等幅フォントに比べて文字幅が狭めなので、同じ領域に表示できる文字数が多い。

などがあり、僕は特に3番目の理由が特に気にいっていて、
はじめは少し違和感があったのですが慣れてくると、
カスタマイズなしでも他のフォントがつかえなくなるぐらいでした。

カスタマイズ方法

カスタムビルドはTOMLで書かれた設定ファイルを1つ用意するだけ済みます。
簡単なのはWeb上でIosevka Customizerから設定ファイルを生成するやり方です。

https://typeof.net/Iosevka/customizer

Family Name、Serifs、Spacing、Weights、Slopes、Width Optionsなどをよしなに決めたら、Character Variantsの選択です。

Character Variants の選択

例えばアルファベット小文字のaには14種類のバリアント(v26.1.0時点)があり、次のような画面から好みのバリアントを選択します。

variant picker of the Iosevka Customizer

1..6は二階建て、7..14は一階建てで、それぞれ爪や鱗の形状など細かな違いがあります。

リガチャの選択

ligations preview of the Iosevka Customizer

C-likeやJavaScript、Haskellなど言語ごとに用意されたプリセットを使用するか、あるいは
->!=<=などの合字を個別に有効化できます。

設定が終わったら最後に出力されたtomlファイルをprivate-build-plans.tomlとして保存します。

ビルド

ビルド方法はローカルで行うか、Dockerイメージ内で行うかの2つがありますが、自分の環境で試した限りDockerではエラーが出てうまく行かないのでここではローカルの方を解説します。

レポジトリのクローン

git clone --depth 1 https://github.com/be5invis/Iosevka.git 
cd Iosevka

Requirement

Macであればbrewで入ります。

brew install ttfautohint
  • node/npm

Build customized fonts

まずは依存パッケージのインストール

npm i

先ほど生成したprivate-build-plans.tomlをプロジェクトルート(Iosevka/private-build-plans.toml)に配置した状態でビルド開始です。

# 全部(ttf + webfont)
npm run build -- contents::<your-plan-name>

# または ttfのみでよい場合
npm run build -- ttf::<your-plan-name>

かなり時間がかかるので気長に待ちます。☕️

完成したフォントはIosevka/dist配下に置かれているので、これを各OSのやり方でインストールしたら終了です。エディタやターミナルに設定して使用しましょう。

超個人的フォントRiosevkaの紹介

ここからは、以上に基づいてカスタマイズしたRiosevkaという僕が普段使っているプログラミングフォントを紹介させていただきます。

Preview of Riosevka by Iosevka Customizer

Another preview of Riosevka by Iosevka Customizer

private-build-plans.toml
[buildPlans.riosevka]
family = "Riosevka"
spacing = "normal"
serifs = "sans"
no-cv-ss = true
export-glyph-names = false

[buildPlans.riosevka.variants.design]
capital-a = "straight-serifless"
capital-b = "standard-serifless"
capital-c = "serifless"
capital-d = "standard-serifless"
capital-e = "serifless"
capital-f = "serifless"
capital-g = "toothless-corner-serifless-hooked"
capital-h = "serifless"
capital-i = "short-serifed"
capital-j = "flat-hook-serifed"
capital-k = "symmetric-touching-serifless"
capital-l = "serifless"
capital-m = "flat-bottom-serifless"
capital-n = "standard-serifless"
capital-p = "closed-serifless"
capital-q = "straight"
capital-r = "straight-serifless"
capital-s = "serifless"
capital-t = "serifless"
capital-u = "toothless-rounded-serifless"
capital-v = "straight-serifless"
capital-w = "straight-flat-top-serifless"
capital-x = "straight-serifless"
capital-y = "straight-serifless"
capital-z = "straight-serifless"
a = "double-storey-toothless-corner"
b = "toothless-corner-serifless"
c = "serifless"
d = "toothless-corner-serifless"
e = "flat-crossbar"
f = "flat-hook-extended"
g = "single-storey-flat-hook-earless-corner"
h = "straight-serifless"
i = "zshaped"
j = "serifless"
k = "symmetric-touching-serifless"
l = "zshaped"
m = "earless-single-arch-serifless"
n = "earless-corner-straight-serifless"
p = "earless-corner-serifless"
q = "earless-corner-straight-serifless"
r = "hookless-serifless"
s = "serifless"
t = "flat-hook-short-neck"
u = "toothless-corner-serifless"
v = "straight-serifless"
w = "straight-flat-top-serifless"
x = "straight-serifless"
y = "straight-serifless"
z = "straight-serifless"
long-s = "bent-hook-serifless"
eszet = "sulzbacher-descending-serifless"
lower-eth = "straight-bar"
lower-thorn = "serifless"
lower-alpha = "crossing"
capital-gamma = "serifless"
capital-delta = "straight"
lower-delta = "flat-top"
lower-iota = "zshaped"
capital-lambda = "straight-serifless"
lower-lambda = "straight"
lower-mu = "toothless-corner-serifless"
lower-xi = "rounded"
lower-chi = "straight-serifless"
cyrl-capital-zhe = "symmetric-touching"
cyrl-zhe = "symmetric-touching"
cyrl-capital-ze = "serifless"
cyrl-ze = "serifless"
cyrl-capital-ka = "symmetric-connected-serifless"
cyrl-ka = "symmetric-touching-serifless"
cyrl-el = "straight"
cyrl-em = "flat-bottom-serifless"
cyrl-en = "serifless"
cyrl-capital-u = "straight-serifless"
cyrl-ef = "serifless"
cyrl-che = "standard"
cyrl-yeri = "corner"
cyrl-yery = "corner"
cyrl-capital-ya = "straight-serifless"
cyrl-ya = "straight-serifless"
zero = "slashed"
one = "no-base"
two = "straight-neck"
three = "two-arcs"
four = "closed"
five = "vertical-upper-left-bar"
six = "straight-bar"
seven = "straight-serifless"
eight = "crossing-asymmetric"
nine = "straight-bar"
diacritic-dot = "round"
punctuation-dot = "round"
tilde = "low"
asterisk = "penta-mid"
underscore = "high"
caret = "low"
paren = "normal"
brace = "straight"
number-sign = "upright-tall"
ampersand = "closed"
at = "threefold"
dollar = "through"
percent = "rings-continuous-slash"
bar = "natural-slope"
ascii-single-quote = "straight"
ascii-grave = "straight"
question = "smooth"
pilcrow = "low"
cent = "through"
partial-derivative = "straight-bar"
micro-sign = "toothless-corner-serifless"
lig-ltgteq = "flat"
lig-neq = "slightly-slanted"
lig-equal-chain = "without-notch"
lig-hyphen-chain = "without-notch"
lig-double-arrow-bar = "without-notch"
lig-single-arrow-bar = "without-notch"

[buildPlans.riosevka.variants.italic]
c = "serifless"
f = "flat-hook-tailed"
i = "serifed-diagonal-tailed"
j = "flat-hook-serifless"
l = "serifed-diagonal-tailed"
q = "earless-corner-diagonal-tailed-serifless"
t = "diagonal-tailed-short-neck"
y = "straight-turn-serifless"
long-s = "bent-hook-tailed"
eszet = "sulzbacher-tailed-serifless"
lower-iota = "serifed-diagonal-tailed"
cyrl-el = "tailed"
cyrl-capital-u = "straight-turn-serifless"
cyrl-che = "tailed"

[buildPlans.riosevka.ligations]
inherits = "javascript"

コンセプト

「統一感」と「フラット」がこのフォントのコンセプトです。

これを実現するため、以下のような方針に従ってカスタマイズしています。

  • sans-serif体(slab体だと小さな文字ではセリフが潰れてごちゃっとした印象になりがち)
  • 直線的かつ判別しやすいグリフ
  • グリフの重心が中心から離れすぎないようにする(全体的に見たときにちぐはぐな印象になるのを防ぐ)

こだわりポイント

Corner

サンセリフ体で直線的なバリアントを探すに当たって考慮するべき問題に線同士の繋ぎ目の形状があります。
たとえばa,d,uの右下や、bの左下、n,m,pの左上、g,qの右上などです。

各グリフのこの部分には数種類かの共通したバリアントが用意されています。

pを例とすると、

  1. serifed(tailed)
    eared-motion-serifed
  2. toothed(eared)
    eared-serifless
  3. corner
    earless-corner-serifless
  4. rounded
    earless-rounded-serifless

これらのうち1,2はセリフ(ear)がついているので脱落、4は直線的という条件に反するので脱落。
よって、3のcornerを選ぶことにしました。

他の文字についてもcornerが用意されているものはできるだけcornerを選ぶことで統一感を演出しています(rについては判別性の観点から耳ありを採用)。

Longer f

20種類のバリアント(v26.1.0時点)のうち、方針に合うのは次の4種類です

  1. flat-hook-serifless
    flat-hook-serifless

  2. flat-hook-serifless-crossbar-at-x-height
    flat-hook-serifless-crossbar-at-x-height

  3. flat-hook-extended
    flat-hook-extended

  4. flat-hook-extended-crossbar-at-x-height
    flat-hook-extended-crossbar-at-x-height

1,2がbaselineに収まる高さなのに対して3,4は足がbaselineよりも下まで延長されています。
また1,3は横棒がxの高さよりも低い位置(tの横棒よりも下)にありますが、2,4はxの高さと同じ位置にあります。

ここで僕は3を選びました。

理由はプログラミング言語のキーワードにおけるfの出現率です。

functiondef,funcのような関数宣言のキーワードにはほぼ必ずと言っていいほどfが登場し、また単語の頭や末尾に置かれるために視覚的な重要性が高いと思い、そこに敢えて垂直方向に長く特徴的なグリフをおくことでキーワードの判別性やかっこよさ(諸説)を生み出しています。

僕は普段Rustをよく書いていて、fnという2文字のキーワードがそのままだと寂しいのですが、特徴的なfにとってfnがより引き締まって見えるのが気に入っています。

4ではなく3を選んだのは、重心をより中央に近づけるためです。
これによってfloatのような単語でflの間がスカスカに見えるのを多少防いでいます。

preview of a effect of long-f

リガチャ

リガチャについては無理に詰め込もうとするのではなく、CやRust、JavaScript/TypeScriptを普通に書いていて登場する記号類に限って有効化しています。

たとえば、Haskell系の関数型言語において/=はinequallityを表しますが、C-likeな言語においては除算代入を表すという相違により、このようなものについては混乱を避けるためにリガチャを有効化していません。

完成したフォント

すっきりとしたクセのないフォントに仕上がって満足です。

  • Rust

Rust code example

  • TypeScript

TypeScript code example

Reference

記事中の一部の画像は https://github.com/be5invis/Iosevka からの引用

Discussion