にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

OpenTypeフォントでFizzBuzz(その2)

前に作成した FizzBuzz フォントについて、もっと簡潔にかける方法があったので、改良しました。前の分は次のページを参照ください。

改良点としては、単語の最初あるいは最後について置換を行うことが、より簡潔な記述になりました。この書き方については次のページにまとめています。

サンプルデータ

Download fizzbuzz_1.10.zip (2015-05-10版)

サンプルデータでは M+ OUTLINE FONTS の mplus-1p-regular.ttf の字形データを利用しています。

サンプルデータについて

  • fizzbuzz_1.10.otf が完成したフォントファイルです。
    • このフォントファイルをインストールし(FizzBuzz font regular)、‘liga’を適用して数字を表示させるとFizzBuzzが動くはずです。
  • fizzbuzz_gsub-excluded.otf はフィーチャーファイルを適用する前のフォントデータ
  • fizzbuzz.fea はフィーチャーファイルです。
  • makefont.py は Python スクリプトで、 FontForgePython から利用することによって fizzbuzz_gsub-excluded.otf に fizzbuzz.fea を適用して fizzbuzz_1.01.otf を作成します。

ここではフィーチャファイルの適用に FontForge を利用していますが、同様のフォントオーサリングソフトでも可能だと思います。なお FontForgeGUIからフォントを出力したところGSUBが正常に出力されませんでした。

実行例

以下に実際に動かしている例を示します。 Firefox、あるいはChrome, SafariなどのWebkitブラウザで表示するとFizzBuzzが正しく表示されるはずです。スマートフォンからの場合はこちらのページをご覧ください。

コピーしてテキストエディタ等に貼り付けてみて下さい。

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100
また、下のテキストエリアに数字を入力して動作を確認することもできます。

制限(変更なし)

  • 32ケタまでの数にしか対応していません。それより大きい数ではFizz、Buzzは出力されますが先頭の数字が消えずに残ります。
  • 正の整数にしか対応していません。

解説

グリフの用意(以前と変更なし)

3の倍数の判定のため、先頭からその位置までの各位の数字の和を3で割った余りを記録させる必要があり、そのためのグリフを追加しています。(<数字の英語名>_<0,1,2>mod3 という名前にしてあります)

普通の数字(@num_normal) 0 1 2 3 4 5 6 7 8 9
剰余0(@num_0) 00 10 20 30 40 50 60 70 80 90
剰余1(@num_1) 01 11 21 31 41 51 61 71 81 91
剰余2(@num_2) 02 12 22 32 42 52 62 72 82 92

また、 Fizz, Buzz, FizzBuzz を表示するためのグリフ Fizz.liga, Buzz.liga, FizzBuzz.liga を追加しています。
加えて、 不要な数字を消すための何も表示されない幅0のグリフ nullchar を追加しています。

GSUBの設定

サンプルデータ内の fizzbuzz.fea ファイルによってGSUBを定義しています。詳しくは当該ファイルを参照してください。

最初の桁を3の剰余が記録された数に変更

check_first_fig_mod3:

ignore substitute @num_all @num_normal';
substitute @num_normal' by @num_after_0;

ignore substitute を指定することで、最初の桁だけに対して置換を行い、3の剰余が記録された数字に置換します。


これを 1234567890987654321 に適用すると次図のようになります。

3の剰余の記録

「各位の数字の和が3で割り切れれば、その数は3の倍数」であることにより、3の倍数を判定します。

このため、先頭から数を追っていって、先頭からその桁までの数字の和の3で割った余りを記録します。これを記録するために3状態を表現できるようグリフを追加してあります。

状態遷移は次図のようになります。

これに沿って、前の数字の状態(@num_0, @num_1, @num_2 クラス)から現在見ている数字の置換先(それぞれ @num_after_0, @num_after_1, @num_after_2 クラス)を定義していて、それによって置換することで3で割った余りを記録します。

今の数字 0 1 2 3 4 5 6 7 8 9
前が剰余0(@num_0)の時の置換先@num_after_0 00 11 22 30 41 52 60 71 82 90
前が剰余1(@num_1)の時の置換先@num_after_1 01 12 20 31 42 50 61 72 80 91
前が剰余2(@num_2)の時の置換先@num_after_2 02 10 21 32 40 51 62 70 81 92

check_mod3:

substitute @num_0 @num_normal' by @num_after_0;
substitute @num_1 @num_normal' by @num_after_1;
substitute @num_2 @num_normal' by @num_after_2;

前の段階で変更した最初のグリフを足がかりにして、3の剰余の情報をもった数字に置換していきます。

Fizz Buzz の判定

check_fizzbuzz:

ignore substitute @num_all' @num_all; 
substitute [\zero_0mod3 \five_0mod3]' by \FizzBuzz.liga;
substitute @num_0_not0mod5' by \Fizz.liga;
substitute [\zero_1mod3 \five_1mod3 \zero_2mod3 \five_2mod3]'
	   by \Buzz.liga;

最後の桁について、その剰余が0かそれ以外か(=3の倍数かどうか)と、その数字が「0, 5」かそれ以外か(=5の倍数かどうか)を見て、最後の桁を適切に Fizz, Buzz, FizzBuzz に置き換えます。そうでなければそのまま。

置換元 00 10 20 30 40 50 60 70 80 90
置換先 FizzBuzz Fizz Fizz Fizz Fizz FizzBuzz Fizz Fizz Fizz Fizz
置換元 01 11 21 31 41 51 61 71 81 91
置換先 Buzz - - - - Buzz - - - -
置換元 02 12 22 32 42 52 62 72 82 92
置換先 Buzz - - - - Buzz - - - -

数字の消去

最後の文字が数字ではなく Fizz, Buzz, FizzBuzz のいずれかであった場合は、それ以前の数字を消す必要があります。そのため、数字を段階的に nullchar (幅0で何も表示要素のないグリフ) に置き換えて、数字が表示されないようにします。


delete_figures_2pow4: 数字でない文字(@chr_normal)の前に16個の数字が並んでいる場合、その最初の数字を nullchar にして非表示にする

substitute @num_all' @num_all @num_all @num_all 
	   @num_all @num_all @num_all @num_all
	   @num_all @num_all @num_all @num_all
	   @num_all @num_all @num_all @num_all
	   @chr_notnum by \nullchar;

delete_figures_2pow3: 数字でない文字(@chr_notnum)の前に8個の数字が並んでいる場合、その最初の数字を nullchar にして非表示にする

substitute @num_all' @num_all @num_all @num_all
	   @num_all @num_all @num_all @num_all
	   @chr_notnum by \nullchar;

delete_figures_2pow2: 数字でない文字(@chr_notnum)の前に4個の数字が並んでいる場合、その最初の数字を nullchar にして非表示にする

substitute @num_all' @num_all @num_all @num_all
	   @chr_notnum by \nullchar;

delete_figures_2pow1: 数字でない文字(@chr_notnum)の前に2個の数字が並んでいる場合、その最初の数字を nullchar にして非表示にする

substitute @num_all' @num_all @chr_notnum by \nullchar;

delete_figures_2pow0: 数字でない文字(@chr_notnum)の前に1個の数字が並んでいる場合、その数字を nullchar にして非表示にする

substitute @num_all' @chr_notnum by \nullchar;


実際に 1234567890987654321 がどのように表示されるか確認してみましょう:

1234567890987654321

(2016-10-11 追記)

Google DriveのWebホスティング機能の廃止により正しく見えない状態になっていたのを修正しました。