Iosevkaをカスタマイズして至高のプログラミングフォントをつくる
なぜテーマを自作するヲタクは多いのに、フォントにこだわる人は少ないのか。
Iosevkaについて
Iosevkaはオープンソースで開発されているプログラマブルなフォントです。
特徴としては、
- Normal/Term/Fixed(全角文字やリガチャの扱い方)、Sans/Slab、Etoile/Aile(プロポーショナルフォント)、などの様々なスタイル
- 文字ごとに用意されたvariantや、リガチャごとのon/offを自分でカスタマイズしてビルドできる。
- 一般的な等幅フォントに比べて文字幅が狭めなので、同じ領域に表示できる文字数が多い。
などがあり、僕は特に3番目の理由が特に気にいっていて、
はじめは少し違和感があったのですが慣れてくると、
カスタマイズなしでも他のフォントがつかえなくなるぐらいでした。
カスタマイズ方法
カスタムビルドはTOMLで書かれた設定ファイルを1つ用意するだけ済みます。
簡単なのはWeb上でIosevka Customizerから設定ファイルを生成するやり方です。
Family Name、Serifs、Spacing、Weights、Slopes、Width Optionsなどをよしなに決めたら、Character Variantsの選択です。
Character Variants の選択
例えばアルファベット小文字のa
には14種類のバリアント(v26.1.0時点)があり、次のような画面から好みのバリアントを選択します。
1..6は二階建て、7..14は一階建てで、それぞれ爪や鱗の形状など細かな違いがあります。
リガチャの選択
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
という僕が普段使っているプログラミングフォントを紹介させていただきます。
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
を例とすると、
-
serifed
(tailed
)
-
toothed
(eared
)
-
corner
-
rounded
これらのうち1,2はセリフ(ear)がついているので脱落、4は直線的という条件に反するので脱落。
よって、3のcorner
を選ぶことにしました。
他の文字についてもcorner
が用意されているものはできるだけcorner
を選ぶことで統一感を演出しています(r
については判別性の観点から耳ありを採用)。
f
Longer 20種類のバリアント(v26.1.0時点)のうち、方針に合うのは次の4種類です
-
flat-hook-serifless
-
flat-hook-serifless-crossbar-at-x-height
-
flat-hook-extended
-
flat-hook-extended-crossbar-at-x-height
1,2がbaselineに収まる高さなのに対して3,4は足がbaselineよりも下まで延長されています。
また1,3は横棒がx
の高さよりも低い位置(tの横棒よりも下)にありますが、2,4はxの高さと同じ位置にあります。
ここで僕は3を選びました。
理由はプログラミング言語のキーワードにおけるf
の出現率です。
function
やdef
,func
のような関数宣言のキーワードにはほぼ必ずと言っていいほどfが登場し、また単語の頭や末尾に置かれるために視覚的な重要性が高いと思い、そこに敢えて垂直方向に長く特徴的なグリフをおくことでキーワードの判別性やかっこよさ(諸説)を生み出しています。
僕は普段Rustをよく書いていて、fn
という2文字のキーワードがそのままだと寂しいのですが、特徴的なf
にとってfn
がより引き締まって見えるのが気に入っています。
4ではなく3を選んだのは、重心をより中央に近づけるためです。
これによってfloat
のような単語でf
とl
の間がスカスカに見えるのを多少防いでいます。
リガチャ
リガチャについては無理に詰め込もうとするのではなく、CやRust、JavaScript/TypeScriptを普通に書いていて登場する記号類に限って有効化しています。
たとえば、Haskell系の関数型言語において/=
はinequallityを表しますが、C-likeな言語においては除算代入を表すという相違により、このようなものについては混乱を避けるためにリガチャを有効化していません。
完成したフォント
すっきりとしたクセのないフォントに仕上がって満足です。
- Rust
- TypeScript
Reference
記事中の一部の画像は https://github.com/be5invis/Iosevka からの引用
Discussion