はじめに
カレンダーの一つの月の日付の並びには、各曜日始まりの7種×日数の種類(28, 29, 30, 31日)の4種で合計28種ある。祝日などを考えなければ28種を使いまわすことで任意の年月に対応できる。
そんな発想で作られた、使いまわしできるカレンダーを万年カレンダーという(日数の違いにまで対応してるとは限らないが)。
という訳で、せっかくなのでこの万年カレンダーをOpenTypeフォントで実装してみる。
2016-01とか2016/01とか書くと2016年1月のカレンダーが表示されるようにしたい。
完成品
ダウンロード
- Acalendar.ttf (72.9 KB)
ソースコード
GitHubに挙げたのでそちらを参照。
github.com
- Acalendar.ttf: フォントデータ
- Acalendar.sfd: Fontforgeファイル (GSUB, GPOSの設定なし)
- calendar.fea: GSUB, GPOSの定義
方針
togetter.com
ここで色々検討されているが、カレンダーが28種類のなかからどれを使うかは、
- その年の元日の曜日は何か
- その年は閏年かどうか
- 何月か
によって決まる。
元日の曜日は400年ごとに同じ並びが繰り返され、次のような計算式で計算できるっぽい。(なお、日曜のoffset=3は間違い)
正整数Yについて、Y年1月1日の曜日wは、 y' = Y - 1 mod 400 w = y' + ⌊y' / 4⌋ - ⌊y' / 100⌋ + offset mod 7 で求められるっぽい。ただしoffset=2だと土曜が0, offset=3だと日曜が0となる。
— にせねこ (@nixeneko) 2017年1月2日
閏年かどうかは、西暦の年の数字が
- 400で割り切れるときは閏年
- 400で割り切れない、かつ、100で割り切れるときは閏年ではない
- 400で割り切れない、かつ、100で割り切れない、かつ、4で割り切れるときは閏年
- それ以外は(4で割り切れないとき)は閏年ではない
という風に判定でき、
- 4で割り切れる
- 100で割り切れる
- 400で割り切れる
かどうかの3つを組み合わせて判定できる。
OpenTypeのGSUBによる置換を使って剰余等の計算を真面目に行ってもいいのだろうが、非常に面倒臭そうだし、状態の数が結構な数になってしまいそうなので、今回は西暦の年を400で割った余りについて、400通りの置換を定義することで行う。3桁未満にも対応できるように実際には置換の数はもっと増える(というか、499個)。
実装
グリフの用意
実装してみる。
アウトラインとしてM+ Fontsの mplus-1p-medium.ttf をベースにした。
まずは始まる曜日の違い(7種)と月の日数の違い(4種)で28種類のカレンダーを用意する。
最初にグリフの用意。
日曜始まりでカレンダーを用意した。
始まる曜日と日数で、グリフ名を
cal_mon_28
など、
cal_(曜日)_(日数)
とした。
GSUBの実装
詳しくは、GitHubに上げた calendar.fea を参照されたい。
最初に年について400の剰余を計算する。
これは要するに、百の位以上の桁について4の剰余を計算すればよい。
4の剰余を表すための状態の数だけ状態に対応する数字のグリフを作成する。
数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
0 mod 4 | 00 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 |
1 mod 4 | 01 | 11 | 21 | 31 | 41 | 51 | 61 | 71 | 81 | 91 |
2 mod 4 | 02 | 12 | 22 | 32 | 42 | 52 | 62 | 72 | 82 | 92 |
3 mod 4 | 03 | 13 | 23 | 33 | 43 | 53 | 63 | 73 | 83 | 93 |
実際には zero_0mod4, one_0mod4, …, nine_0mod4, …, zero_3mod4, …, nine_3mod4 などという名前のグリフを用意した。
数字の並びについて、まず前に数字が続かないもの、つまり数字の最初の桁について置換をおこなう。3桁の数字の最初の文字について次のように置換をおこなう。
元の数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
置換先 | 00 | 11 | 22 | 33 | 40 | 51 | 62 | 73 | 80 | 91 |
これは lookup check_first_fig_mod4 で行っている。
次に、4の剰余の状態を持った数字の次に来る3つの数字のうちの1番目の数字について置換を行う。
つまり、
mod4の数字 普通の数字 普通の数字 普通の数字
という並びの時に太字にした“普通の数字”を対応するmod4の数字に置換する。
元の数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
前が剰余0か2の時の置換先 | 00 | 11 | 22 | 33 | 40 | 51 | 62 | 73 | 80 | 91 |
前が剰余1か3の時の置換先 | 02 | 13 | 20 | 31 | 42 | 53 | 60 | 71 | 82 | 93 |
これは lookup check_mod4 で行った。
これで年の400の剰余が求まった。
さて、次に、年の始まりの曜日と閏年かどうかの14状態を判定する。これは、その14状態に対応するスラッシュ“/”のグリフを用意し、年と月の間のスラッシュ“/”(あるいはハイフンでもいい)に対し置換を行うこととする。これはスラッシュの前3桁の数字について400通り(数字が2桁、1桁しかない場合を加えて499通り)の置換をずらずら列挙することになる。うっ頭が…。
これは lookup check_firstday_and_leapyear で実装した。気合、といった感じ。
状態をもつスラッシュのグリフ名は
slash_mon_leap slash_tue_comm
などとした。(mon, tue, wed, thu, fri, sat, sun)×(leap, comm)
置換後、スラッシュが14の状態をもっている。閏年はl, 閏年でない年はnで示した。
スラッシュの14状態(一番左の列)に対して、後続する数字(一番上の行)によって次の表のようにカレンダーを表示する。表記したのはカレンダーに含まれる日数と開始曜日である。
01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
日, n | 31日 | 28水 | 31水 | 30土 | 31月 | 30木 | 31土 | 31火 | 30金 | 31日 | 30水 | 31金 |
月, n | 31月 | 28木 | 31木 | 30日 | 31火 | 30金 | 31日 | 31水 | 30土 | 31月 | 30木 | 31土 |
火, n | 31火 | 28金 | 31金 | 30月 | 31水 | 30土 | 31月 | 31木 | 30日 | 31火 | 30金 | 31日 |
水, n | 31水 | 28土 | 31土 | 30火 | 31木 | 30日 | 31火 | 31金 | 30月 | 31水 | 30土 | 31月 |
木, n | 31木 | 28日 | 31日 | 30水 | 31金 | 30月 | 31水 | 31土 | 30火 | 31木 | 30日 | 31火 |
金, n | 31金 | 28月 | 31月 | 30木 | 31土 | 30火 | 31木 | 31日 | 30水 | 31金 | 30月 | 31水 |
土, n | 31土 | 28火 | 31火 | 30金 | 31日 | 30水 | 31金 | 31月 | 30木 | 31土 | 30火 | 31木 |
日, l | 31日 | 29水 | 31木 | 30日 | 31火 | 30金 | 31日 | 31水 | 30土 | 31月 | 30木 | 31土 |
月, l | 31月 | 29木 | 31金 | 30月 | 31水 | 30土 | 31月 | 31木 | 30日 | 31火 | 30金 | 31日 |
火, l | 31火 | 29金 | 31土 | 30火 | 31木 | 30日 | 31火 | 31金 | 30月 | 31水 | 30土 | 31月 |
水, l | 31水 | 29土 | 31日 | 30水 | 31金 | 30月 | 31水 | 31土 | 30火 | 31木 | 30日 | 31火 |
木, l | 31木 | 29日 | 31月 | 30木 | 31土 | 30火 | 31木 | 31日 | 30水 | 31金 | 30月 | 31水 |
金, l | 31金 | 29月 | 31火 | 30金 | 31日 | 30水 | 31金 | 31月 | 30木 | 31土 | 30火 | 31木 |
土, l | 31土 | 29火 | 31水 | 30土 | 31月 | 30木 | 31土 | 31火 | 30金 | 31日 | 30水 | 31金 |
14 × 12 = 168通り。一桁の数字にも対応するために126通りの置換を追加し計294通りになった。
これは lookup replace_calendar で行った。
さて、カレンダーを表示するのもいいけどせっかくなので何年何月ってのも表示させたい。
月については、事前に月を示す(JAN, FEB, などと表示される)グリフを用意しておき、グリフ幅0で、カレンダーのグリフの次にきたときに丁度いい位置に月の表示がくるように位置調整しておく。
これを、カレンダーのグリフに後続する2桁または1桁の数字に対して置換をおこなうことで、月の表示を可能にしている。
これは lookup replace_month で実装した。
最後に年の表示。数字をspacingなグリフにしてしまうと、並べるのは簡単だが、左側に空白ができてしまう。
という訳で、年の数字専用の幅0のグリフを用意しておき、GSUBで数字をそれらのグリフに置換し、その後GPOSのMarkToMark positioning (タグでいうと'mkmk')を使って位置調整することで年の数字を並べようと考えた。
さて、フォントが一応できたので動作を見てみる。
Adobe InDesign
「文字」パネルプルダウンメニューから「欧文合字」をオンにすると……
やったー!
Adobe Illustrator
イラレではどうかな?OpenTypeパネルから欧文合字を有効にして……
あれー?
'mkmk'が効いてない。
もし'mark'は効くであれば、'mkmk'じゃなくても'mark'とGSUBによる置換で代用することはできるのだけれど。
今回は、GSUBだけである程度まで動くようにしておく。事前に位置をずらした数字を用意しておいて、GSUBで順番に置換する。5桁あれば十分だろう。それより大きい桁数は'mkmk'を有効にできる環境で使うようにしてもらう。
これによって年を表す数字のグリフが5倍に増える。
このあたりは lookup replace_year_10000, lookup replace_year によってGSUB置換を実装、さらに lookup STACK_YEAR を使って6桁以上の年の数字が右に並ぶようにした。
結果
Adobe Illustrator
やったー!
もちろん年が6桁以上になるのは文字が重なってうまく表示できないのだけど(10万年後もグレゴリオ暦が使われてるのか、閏年の計算も同じなのか謎だけど)。
Adobe InDesign
もちろんInDesignでは6桁以上も'mkmk'でうまく並ぶ。年後も地球が存在してるのか知らんけど。ちなみに太陽の寿命が年位らしい。