にせねこメモ

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

初めてのTrueType命令: Windowsでは見えないフォントをつくる

TrueType命令を利用して何も表示されないフォントを作った。
f:id:nixeneko:20161216002924p:plain

フォント概要

作成したフォントは、アウトライン情報は保持しているのだが、TrueType命令によってすべての点をグリフの原点へ移動し、描画されるビットマップが存在しない状態にすることで何も表示されなくしている。

Windowsでは何も表示されなくなる一方で、Macではヒント命令を実行しないので普通に文字が表示される。そのため、フォントレンダラがレンダリング時にTrueTypeヒント命令を実行するどうかを確認するために用いることができる。

ダウンロード

作り方・技術解説

まえがき

最近TrueType命令を触っている。
TrueType命令というのは、OpenTypeフォント規格でアウトラインの形式としてTrueTypeを選択した場合に使用できるヒンティング用プログラム言語である。もともとTrueTypeフォント規格に付随していた。
ヒンティングでは主に制御点を動かすことでレンダリングされるビットマップを調整するのだが、この命令セットが妙に高機能なので色々できそうだ、という訳である。

このTrueType命令だが、最近のmacOS (OS X)では単に無視されるらしい。ヒンティング自体が低解像度の画面で可読性の高い表示を行うためのものなので、高密度なretinaディスプレイの下ではお役御免といったところだろうか。

という訳で、それを使って何か作ってみよう、というのがこの記事の目的である。

なお、記事タイトルの「初めてのTrueType命令」というのは私が触るのが初めてだったからであり、初めての人がこの記事を読んでTrueType命令を書けるようになるかは定かでない。

方針

さて、見えないフォントを作ることにする。つまり、アウトライン情報は保持しているのだが、TrueType命令によってすべてを指定した点(グリフの原点)に移動してしまうことで描画されるビットマップが存在しない状態にしよう、というものである。

TrueType命令を実行しない場合にはグリフが表示されるため、レンダリングしている環境でTrueType命令が実行されているかを確認することに使える。前述したようにTrueType命令は最近のMacでは無視されるので、Windowsでは見えないフォントということになる。

Fontforgeのインストール

TrueType命令のデバッグにはFontforgeが使える。最近のWindowsインストーラでインストールされるものにはTrueTypeデバッガを有効にしてコンパイルしてあるもののようなのでインストールすればそのまま使える。今回はWindows版の2016-10-04 Release Installer (.exe)でインストールし使用した。

コンパイル方法については次のページに書いてあるのでもし必要があれば参照されたい。


また、必須ではないのだが、Fontforgeはファイルメニューの環境設定から「TTF」タブの「大変更時に命令を消去」をオフにしておいた方がいいかもしれない。というのは、誤って制御点を追加してしまい編集中の命令が全消去された経験があるからである。
f:id:nixeneko:20161215202944p:plain

使うフォントの用意

さて、制御点を動かすにはTrueType形式のアウトラインが必要である。
今回はM+フォントをダウンロードしてきて弄ることにする。mplus-1p-regular.ttfをFontforgeで開く。

Fontforgeのヒント命令編集機能

メインウィンドウの「ヒント」メニューからヒント命令の編集ができる。
f:id:nixeneko:20161215203613p:plain


また、アウトラインウィンドウの「ヒント」メニューで個別グリフのヒント命令を細かく編集したりデバッグを行うことができる。
f:id:nixeneko:20161215203829p:plain

前準備

さて、まずはヒント命令を削除する。
メインウィンドウの「ヒント」→「Remove Instr Tables」でTrueType命令を全削除できる。

また、M+フォントはなぜかEMサイズが1000なので、2の冪数である1024にしておく。「エレメント」→「フォント情報」からウィンドウを開き、「一般情報」タブで「EMの大きさ」を1024にしてOKを押す。

TrueType命令を書いていく

制御点の移動命令

さて、実際にTrueType命令を書いていこうと思う。

今回は、グリフの全ての点を原点(0.0, 0.0)に移動することがゴールである。
そのためには制御点を移動する必要がある。制御点を移動する命令は豊富にあるが、今回は座標を指定してそこに移動する命令SCFS[]を使うことにする。
仕様によると、命令は次の様になっている。

SCFS[] Sets Coordinate From the Stack using projection vector and freedom vector

Pops c:coordinate value (F26Dot6)
p:point number (uint32)
Pushes なし
Uses zp2, freedom vector, projection vector

座標c, 制御点番号pを受け取り、pで表される制御点を座標cに移動するというものである。制御点はfreedom vectorに沿って移動され、projection vectorに沿った座標がcとなる場所に移される、らしい。

スタック

ところで、TrueType命令を実行するものはスタックマシンである。データはスタックに積み上げられ、命令の引数というのもスタック上に積まれていく。上の表のPopsというのがスタックから取得するデータを表しており、取得した(popされた)データはスタックから取り除かれる。

上の表ではcとpがこの順番でpopされる。すなわち、この命令を実行する前に、スタックトップにはcが、その一つ下にpがある状態になっている必要がある。

                     ↓スタックトップ
 … | 点番号p | 座標c |

スタックにデータを積む(pushする)のは主にPUSHB命令とPUSHW命令を使う。それぞれ、命令に後続する1~8個のByte(8ビット符号なし整数)もしくはWord(16ビット符号あり整数)の値をスタックに積む。積む数値の個数は指定する必要がある(正確には個数によって別命令になっている)。スタックに積む際には数値は32ビットに拡張され、PUSHBでは符号なしの拡張、PUSHWでは符号拡張される。


次の例はFontforgeのTrueType命令編集機能で認識されるように書いたもので、TrueType命令のマニュアルに書かれている表記とも少し異なっている。

PUSHB_3
 1
 2
 3

とすると

              →スタックトップ
 … | 1 | 2 | 3 |

のようにスタックに積まれ、

PUSHW_2
 2456
 -100

とすると

              →スタックトップ
 … | 2456 | -100 |

のように積まれていく。PUSHB_3、PUSHW_2のように命令の右端についている数字はpushする数値の個数を表している。

制御点移動のコード(1)

さて、SCFSを実行するところに戻る。点番号1の制御点を座標0.0に移動するコードは次のようになる。

PUSHB_1
 1
PUSHW_1
 0x0000
SCFS

試しに実行してみよう。

グリフのアウトラインウィンドウの「ヒント」→「ヒント命令を編集」でヒント編集用のウィンドウを開き、「編集」ボタンを押す(ボタンが何も表示されていない場合、ウィンドウサイズを縦に拡大すると現れる)。上のコードをペーストしOKを押す。
次に「ヒント」→「デバッグ」を開く。「グリッド合わせのパラメータ」ダイアログが開くので適当にOKを押す。
f:id:nixeneko:20161215221739p:plain
上図のようにTrueType命令のステップ実行用のインタフェースが現れ、またTrueTypeのパラメータやスタックを表示するウィンドウが開かれる。

ステップ実行のボタンを押していくと命令をひとつづつ実行することができる。実行結果が次である。
f:id:nixeneko:20161215221912p:plain
点番号1の制御点がX座標0.0に移動していることがわかる。


要するに制御点は設定した一つの方向にしか移動できないということである。そのため、x座標軸、y座標軸両方に対して座標を指定して移動するためには、方向を変化させて2度別々に処理をする必要がある。

移動方向切り替え

移動方向を切り替えるコマンドがSVTCAである。

  • STVCA[0]でY軸方向
  • STVCA[1]でX軸方向

に移動ができるようになる。具体的には距離を測る方向(projection vector)と移動する方向(freedom vector)を座標軸に指定するということらしい。

制御点移動のコード(2): 2次元移動

さて、Y軸方向にも移動させてみよう。制御点1を原点(0.0, 0.0)に移動する。コードを次のように書き換える。

STVCA[1]
PUSHB_1
 1
PUSHW_1
 0x0000
SCFS
STVCA[0]
PUSHB_1
 1
PUSHW_1
 0x0000
SCFS

先ほどのコードの前にSTVCAで移動方向を切り替えるものがついて、先にX軸方向、次にY軸方向、と2回繰り返している訳である。

同様に実行してみる。
f:id:nixeneko:20161215223116p:plain
分かりづらいが原点に移動している。


さて、これを制御点の数だけ繰り返せば全ての点が原点に移動し何も表示されなくなるという訳である。
単純に上のコードを、点番号を変化させて制御点の数だけ並べてもいいが、さすがにアホっぽいのと容量が無駄なのでもっと効率化したい。

関数化

ここで関数が登場する。

関数は'fpgm'テーブルにおいて、FDEF命令で定義できる。FDEFはスタックから整数を一つpopして、その番号の関数の定義を行う。関数の終わりはENDFで示される。
例えば関数0の定義だと次のように書く。

PUSHB_1
0
FDEF
 <ここに関数の内容がくる>
ENDF

さて、先ほどの、制御点を指定して原点に移動させる、という機能を関数としてまとめてみたい。
移動する制御点を指定するために制御点番号を入力とする。呼び出されるときにスタックトップが制御点番号であることを期待するということであり、関数から抜ける際には引数として入力された数値はpopされてスタックから取り除かれた状態にする。

つまり、スタックが、コール時には

              ↓スタックトップ
 … | 点番号p |

となり、関数を抜けるときは

    ↓スタックトップ
 … | 

となるように設計する。

関数番号は0として定義してみよう。

PUSHB_1
0
FDEF
STVCA[1]
DUP
PUSHW_1
 0x0000
SCFS
STVCA[0]
PUSHW_1
 0x0000
SCFS
ENDF

ここで、DUPはスタックトップを複製する命令である。以下のようにスタックの中身を考えてみると動作がわかると思う。

PUSHB_1
0
FDEF      /* ...|p| */
STVCA[1]  /* ...|p| */
DUP       /* ...|p|p| */
PUSHW_1   /* ...|p|p|0.0| */
 0x0000
SCFS      /* ...|p| */
STVCA[0]  /* ...|p| */
PUSHW_1   /* ...|p|0.0| */
 0x0000
SCFS      /* ...| */
ENDF

実行前後のスタックは先ほど設計したのと合っている。

今定義した関数0を実行するのはCALL関数を使う。コードは次のようになる。

PUSHB_2
 1
 0
CALL

CALLは関数番号をpopし対応する関数を実行する。このコードでは0は関数番号、1は関数の引数とする制御点番号である。

さて、実際に関数が動くか実行してみよう。前述したとおり関数定義は'fpgm'テーブルに書かないといけない。これはFontforgeのメインウィンドウから「ヒント」→「'fpgm'テーブルを編集...」から設定することができる。ここで先ほどの関数定義を書き込み、OKを押す。
また、グリフのヒント命令を関数を実行する命令に書き換える。
そして実行してみる。
f:id:nixeneko:20161216020353p:plain
先ほどに実行した命令と同じ動作をしているので成功である。

制御点の数だけ繰り返す

さて、これを制御点の数だけ繰り返さないといけない。
単純に指定回数だけ関数の実行を繰り返すならLOOPCALLという命令が使えるのだが、今回はすべての制御点に対して操作を行うため何回目の実行かということを覚えておかないといけないため、条件分岐IFと相対ジャンプJMPRを使って実装することにした*1

疑似コードを次に示す。nmaxはグリフの最大の制御点番号を指定する。

  n_cur = nmax;
loop_label:
  if (ncur >= 0){
    func0(ncur);
    ncur--;
    goto loop_label;
  }

さて、実際にコードを書いてみる。
Aの制御点は最大の制御点番号は11なので、nmaxとして11を指定してやってみる。

PUSHB_1
 11
DUP
PUSHB_1
 0
GTEQ
IF
DUP
PUSHB_1
 0
CALL
PUSHB_1
 1
SUB
PUSHW_1
 -15
JMPR
EIF
POP

GTEQはスタックトップの2項目を比較しその結果をpushする命令である。

GTEQ            →スタックトップ
before …| a | b | 
after  …| a>=b |

a>=bのときは1、そうでないときは0がpushされる。

IFはスタックトップから整数を一つpopし、それが0でないときは次の命令を実行していき、0ならばEIFにジャンプするというものである。

JMPRは整数を一つpopし、それによって指定される相対位置へとジャンプする。マイナスだと遡る方向にジャンプする。

さて、同様にスタックの中身を考えてみる。

         /* …|              */
PUSHB_1  /* …|ncur<=nmax|   */
 11
         /* …|ncur|         */ /* JMPRでここに戻ってくる */
DUP      /* …|ncur|ncur|    */
PUSHB_1  /* …|ncur|ncur|0|  */
 0
GTEQ     /* …|ncur|ncur>=0| */ 
IF       /* …|ncur|         */ /* if ncur >= 0 then */
DUP      /* …|ncur|ncur|    */
PUSHB_1  /* …|ncur|ncur|0|  */
 0
CALL     /* …|ncur|         */ /* func0(ncur) */
PUSHB_1  /* …|ncur|1|       */
 1
SUB      /* …|ncur-1|       */ /* ncur--; */
PUSHW_1  /* …|ncur-1|-15|   */
 -15
JMPR     /* …|ncur-1|       */ /* goto loop_label */
EIF
POP      /* …|              */

良さそう。さてこのプログラムをグリフのTrueType命令に書き込んで実行してみる。
実行しながらスタックの中身を示すウィンドウを見ていくと、スタックの一番下にある値がひとつづつ減少していき最終的に-1となってループを抜けていることがわかる。
f:id:nixeneko:20161216222646p:plain
描画されるビットマップが何もないという状態になっている。成功である。

ループを関数化

さて、これも関数にする。
スタックがコールする前後で、

              →スタックトップ
before:  … | nmax |
after:   … | 

となるようにしよう。nmaxにはグリフの最大の制御点番号を指定する。
関数番号は1とする。

PUSHB_1
 1
FDEF
DUP
PUSHB_1
 0
GTEQ
IF
DUP
PUSHB_1
 0
CALL
PUSHB_1
 1
SUB
PUSHW_1
 -15
JMPR
EIF
POP
ENDF

これは単純に前のコードから、

PUSHB_1
 11

を除いて、

PUSHB_1
 1
FDEF
 ~
ENDF

で囲んだだけである。
これを'fpgm'テーブルに追記する。

さて、関数1をつくったのでグリフから呼ぶ必要がある。
Aの最大の制御点番号は11なので、11を引数として関数1を呼び出す。コードは次のようになる。

PUSHB_2
 11
 1
CALL

実行してみると次のようになる。
f:id:nixeneko:20161216222403p:plain
関数化前と同様に何も塗られるビットマップがないので成功である。


さて、ここではAにだけ消えるようなヒント命令を実装したが、これをすべてのグリフに対しても適用したい。
そこで、ひとまずフォントを出力する。

フォントを出力

フォント出力前に「ヒント」→「'maxp'テーブルを編集...」からmaxpテーブルの数値を編集する。
f:id:nixeneko:20161216000103p:plain

  • ゾーンは2固定。
  • ストレージは使ってないので0。
  • FDEFはさっき関数0, 1の2つを定義したので2としておこう。
  • トワイライトポイントは使ってないので個数0。
  • スタックの最大深さは……使ってたの5個位だった気がするけど多めに指定しておくか。
  • IDEFは使ってないので0。

とか指定する。使ってるインデックスがそれぞれの指定した値を超えるとうまく動かないことになるのだと思う。

フォント情報(エレメント→フォント情報)を適宜修正し、TrueTypeフォントを出力する(ファイル→フォントを出力)。
f:id:nixeneko:20161216000955p:plain

TTXでXMLに変換

さて、これをTTXを使ってXMLファイルに展開する。
TTXのインストールについては次のサイトを参考にされたい。

今回使用したTTXのバージョンは2.5である。
コマンドプロンプトなどで先ほど出力したフォントAdisappear.ttfがあるフォルダへと移動し、

ttx Adisappear.ttf

と指定して展開し、Adisppear.ttxを得る。

すべてのグリフに適用

Adisappear.ttxをテキストエディタで描き、Aのグリフを探し、紐づいているTrueType命令を調べる。<glyf>~</glyf>の中で<TTGlyph name="A">~</TTGlyph>がある。

    <TTGlyph name="A" xMin="36" yMin="0" xMax="674" yMax="748">
      <contour>
        <pt x="36" y="0" on="1"/>
        <pt x="309" y="748" on="1"/>
        <pt x="401" y="748" on="1"/>
        <pt x="674" y="0" on="1"/>
        <pt x="587" y="0" on="1"/>
        <pt x="511" y="218" on="1"/>
        <pt x="196" y="218" on="1"/>
        <pt x="121" y="0" on="1"/>
      </contour>
      <contour>
        <pt x="218" y="284" on="1"/>
        <pt x="488" y="284" on="1"/>
        <pt x="354" y="674" on="1"/>
        <pt x="352" y="674" on="1"/>
      </contour>
      <instructions><assembly>
          PUSH[ ]	/* 2 values pushed */
          11 1
          CALL[ ]	/* CallFunction */
        </assembly></instructions>
    </TTGlyph>

<instructions><assembly>~</assembly></instructions>に挟まれているのがAのグリフに対応する命令であり、その中の11がAの最大の制御点番号である。これはほかのグリフに適用する際、グリフに含まれる制御点<pt>の個数を数えて、(制御点の個数 - 1)個を指定すればよい。


これをすべてのグリフに対しても適用するようなpythonスクリプトを作成した。

#!/usr/bin/env python3
# conding: utf-8

import xml.etree.ElementTree as ET

INFILE = "Adisappear.ttx"
OUTFILE = "Adisappear-out.ttx"
xmltree = ET.parse(INFILE)
xmlroot = xmltree.getroot()

for glyph in xmlroot.find('glyf').findall('TTGlyph'):
    cnt = 0
    for contour in glyph.findall('contour'):
        cnt += len(contour.findall('pt'))
    if cnt > 0:
        prog ="""
          PUSH[ ]    /* 2 values pushed */
          {} 1
          CALL[ ]    /* CallFunction */
          """.format(cnt-1)
        glyph.find('instructions').find('assembly').text = prog

with open(OUTFILE, 'w') as w:
    w.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    xmlstr = ET.tostring(xmlroot, method='xml', encoding="unicode")
    #xmltree.write(OUTFILE)
    w.write(xmlstr)

やってることは各<TTGlyph>に対して<pt>の個数を求めて、もし個数が1以上なら(個数 - 1)を埋め込んだTrueType命令を<instructions><assembly>~</assembly></instructions>間に挿入するというものである。

これをapplyall.pyという名前でAdisappear.ttxと同じフォルダに入れ実行する。pythonは3系でないとうまく動かない。

python3 applyall.py

実行するとAdisappear-out.ttxが出力されるので、これをTTXを使って.ttfファイルへと変換する。

ttx Adisappear-out.ttx

結果

最終的に得られるフォント(Adisppear-out.ttf)をWindows 10でプレビューしたのが次である。
f:id:nixeneko:20161216002924p:plain
やったー! 何も見えないよ……。

他のOSで見てみると……

Mac OS X 10.5 Yosemite

f:id:nixeneko:20161217153953p:plain
噂通りヒンティングはされていないようである。
まあRetinaだし、ヒントでアウトラインを歪めなくてもオリジナルのアウトラインを綺麗に表示できるので……。

Ubuntu Desktop 16.04

f:id:nixeneko:20161216224852p:plain
ヒント効いてない。

どうやら、標準ではヒントが効かなくなっているらしい?

このページによると、gnome-tweak-toolを使ってヒンティングを調整できるようである。
f:id:nixeneko:20161216225417p:plain
「フォント」タブにて、ヒンティングがデフォルトではSlightになっているところをFullに変更する。

そしてフォントビューアを起動しなおすと……
f:id:nixeneko:20161216225543p:plain
やった!

参考サイト

*1:とはいえ、関数の中にカウンタを用意すればLOOPCALLでも問題なくできる気がする。

UbuntuでFontforgeをコンパイル

Ubuntuならapt一発でFontforgeがインストールできるものの、パッケージマネージャで入れられるものにはTrueType命令のデバッガが有効になっていないため、TrueTypeデバッガを使いたければ自前でコンパイルする必要があります。(Appleのヒンティングの特許が2010年に切れたので現在では標準のリポジトリに入ってるものでTrueTypeデバッガが有効になっています。)

この記事はUbuntuにおける、TrueTypeデバッガを有効にしたFontforgeコンパイルの手順を示します。
基本的には書いてあるコマンドを端から実行していけばインストールできるはずです。

環境

OS: Ubuntu 16.04 LTS 日本語 Remix

コンパイル・インストール手順

パッケージのインストール

次のパッケージをインストールします。

  • git
  • m4
  • libtool
  • autoconf
  • automake
  • libglib2.0-dev
  • libgio2.0-cil-dev
  • libfreetype6-dev
  • libxml2-dev
  • libspiro-dev
  • libtiff5-dev
  • libuninameslist-dev
  • libgif-dev
  • libreadline-dev
  • libzmq-dev
  • python-dev
  • potrace
  • libcairo2-dev
  • libpango1.0-dev

端末上で実行するコマンドだと

sudo apt install git m4 libtool autoconf automake libglib2.0-dev \
libgio2.0-cil-dev libfreetype6-dev libxml2-dev libspiro-dev libtiff5-dev \
libuninameslist-dev libgif-dev libreadline-dev libzmq-dev python-dev \
potrace libcairo2-dev libpango1.0-dev

とか。

Fontforgeソースコードの準備

cd ~
git clone git://github.com/fontforge/fontforge.git
cd fontforge
git checkout tags/20161005

git checkoutのところで、リリースを指定します。
Releases · fontforge/fontforge · GitHub
ここなんかを参照しながら好きなリリースを選ぶといいと思います。ここでは最新のリリースである20161005にしています。

Freetypeソースコードの準備

http://download.savannah.gnu.org/releases/freetype/
ここからFreetypeのソースをダウンロードしてきて展開します。

今回はインストールされているlibfreetype6パッケージとバージョンを合わせて2.6.1としました。

wget http://download.savannah.gnu.org/releases/freetype/freetype-2.6.1.tar.gz
tar zxvf freetype-2.6.1.tar.gz 

Fontforgeコンパイル

./configureで--enable-freetype-debuggerオプションをつけ、Freetypeソースのディレクトリを指定することで、TryeType命令のデバッガを有効にしています。

./bootstrap
./configure --enable-freetype-debugger=freetype-2.6.1/
make
sudo make install

Fontforgeがうまく起動できない場合は一度Ubuntuを再起動します。

CygwinでFontforgeをコンパイル

TrueType Instruction用のデバッガがFontForgeには載ってるのだが、有効にするためには自前でコンパイルしないといけないらしい。

という訳で、Cygwinからやってみる。

環境

Fontforgeコンパイル

fontforge/CONTRIBUTING.md at master · fontforge/fontforge · GitHub
ここに書いてある通りにしてコンパイル等をする。

準備

まずはSetup.exe(Setup-x86_64.exe)を使って必要なパッケージをインストール。手元の環境では次のパッケージが追加で必要になった。今までにいろいろ入れてるので更に必要であると思う。

  • m4
  • libtool
  • autoconf
  • automake
  • libspiro-devel
  • libtiff-devel
  • libuninameslist-devel
  • giflib
  • libgif-devel
  • libreadline-devel
  • libzmq-devel
  • python-devel
  • gettext-devel
  • libpango1.0-devel
  • libxml2-devel
  • libiconv-devel

ソースをダウンロード:

  • libfreetype6

また、Cygwinを使ってFontforgeを入れてた場合はアンインストールしておく。

コンパイル&インストール。

基本的には一般的な手順だが、./configureの前に./bootstrapを実行し依存性チェックやconfigureファイルの生成をする必要があるようだ。
また、 ./configure には --enabele-freetype-debugger オプションの設定とFreetypeのソースの入っているディレクトリを設定することでTrueTypeデバッガが有効になるらしい。
./checkoutや./configureの様子を見ながら足りないパッケージは適宜インストールする。

$ cd ~
$ mkdir ff
$ cd ff
$ git clone git://github.com/fontforge/fontforge.git
$ cd fontforge/
$ git checkout tags/20161005
$ ./bootstrap
$ cd ..
$ cp /usr/src/freetype2-2.6.5-1.src/freetype-2.6.5.tar.bz2 .
$ tar jxvf freetype-2.6.5.tar.bz2
$ cd ../fontforge
$ ./configure --enable-freetype-debugger=../freetype-2.6.5
$ make
$ make install

libzmq-devel等をインストールしたものの、./configureが言うには、zeromq (libzmq)のHave?がnoのまま。つまり認識されていない?とりあえずoptionalなライブラリなので無くてもなんとかなるようだが。


cygwinからstartxwinなどでxtermを起動してfontforgeを実行すると無事に起動した。めでたしめでたし。

(2016-11-10 一部修正)

キングブレードX10III Neo分解

f:id:nixeneko:20161016180341j:plain
キンブレ買ったので分解した。分解手順を記録しておく。実際には組み立てていくところを逆から追っていった感じ。

分解したのは、キングブレードX10III Neoのスモーク。


まず先端についてる筒を外す。
f:id:nixeneko:20161016180641j:plain
f:id:nixeneko:20161016180958j:plain

この状態では懐中電灯として使える。
f:id:nixeneko:20161016181007j:plain


リングを外す。なかなか外れず苦労したが、足で柄を抑えて下の縁に爪をひっかけて上に引っ張ったら外れた。うまい具合に上方向に安定した力をかけられるなら何でもいいのだろうが。
f:id:nixeneko:20161016181621j:plain

本体側に溝が掘ってあり、リング裏の出っ張りとかみ合ってリングが回転しないようになっている。そのためリングを外す際にはまっすぐ上に引っ張る必要がある。
f:id:nixeneko:20161016181630j:plain
f:id:nixeneko:20161016182046j:plain


電池蓋を外し、電池を外す。
f:id:nixeneko:20161016182543j:plain

電池が入るところの下部にあるネジ2つを外す。
f:id:nixeneko:20161016182831j:plain


横側の溝にマイナスドライバーの先を突っ込んでこじ開ける。ネジで留まっていた下の方はある程度開くのでそこから入れて少しずつ広げていくといいかもしれない。失敗すると傷がつく(ついた)。逆側も同様にして開くと覆いが外れる。
f:id:nixeneko:20161016183445j:plain

こんな風に切り欠きと出っ張りで嚙み合わさっている。
f:id:nixeneko:20161016183603j:plain
f:id:nixeneko:20161016183743j:plain


開けたところ(先頭部分)。
f:id:nixeneko:20161016184431j:plain

光を均一にするためのレンズを取り外す。LED基盤と一緒に溝にはまってるだけなので引き出す。
f:id:nixeneko:20161016184649j:plain

外したレンズ。
f:id:nixeneko:20161016185002j:plain
f:id:nixeneko:20161016185014j:plain


レンズ外す際に基盤も一緒に外れるが、ここでは嵌めなおしてある。
f:id:nixeneko:20161016184027j:plain
f:id:nixeneko:20161016190140j:plain

下のスイッチ側も外す。ここもスイッチ用の基盤が溝にはまっているだけなので引けば外れる。
f:id:nixeneko:20161016190353j:plain
はんだごてを使わない分解はこれにて終了。


LED基盤上部はこんな感じ。RGBWのフルカラーLEDを搭載している。基盤の裏はアルミ板か何かになっているようで、放熱用だろうか。
f:id:nixeneko:20161016190735j:plain

制御基盤? LED基盤と半田で垂直に固定されている。
f:id:nixeneko:20161016191043j:plain

制御基盤逆面。
f:id:nixeneko:20161016191224j:plain

記事内の画像のライセンス

この記事の画像はCC0ライセンスにより提供されます(2017/03/19~)。

CC0
To the extent possible under law, nixeneko has waived all copyright and related or neighboring rights to キンブレ分解写真. This work is published from: 日本.

さくらのサーバに置いたWebフォントをはてなブログから使う

さくらのレンタルサーバ上にWebフォントのファイルを置いて、それを外部のブログやサイトから参照できるようにする話。

経緯

今年の8/31をもってGoogle DriveのWebホスティングサービスが終了したので、Google Drive上に上げていたWebフォントが使えなくなった。
という訳でさくらのレンタルサーバ上に移行しようとしたのだが、フォントファイルをサーバ上に移動させたものの、はてなブログ(このブログ)からはフォントファイルが読み込まれなかった。

問題点

どうやらクロスドメインによるアクセスが問題のよう。
具体的にはHTTPレスポンスヘッダに“Access-Control-Allow-Origin”がないといけないらしい。
Access-Control-Allow-Originは、それによって指定されたドメインからのアクセスなら読み込んでいいよってものらしい。"*"を指定するとどんなドメインからのアクセスでも読み込めるようになる。

解決方法

現在のさくらのレンタルサーバでは.htaccessでHeaderが効くらしいので、

.htaccessファイルを作成し、

Header set Access-Control-Allow-Origin: "*"

などと書き込んでフォルダと同じディレクトリにぶち込むととりあえずはてなブログからも読み込めるようになる。
セキュリティを考えるとちゃんとドメインを指定したり、フォントに限定して設定できるよう工夫した方がよさそう。

フォントの気持ちになる

フォントの気持ちになるですよ。
f:id:nixeneko:20161007231818p:plain

……正確にはフォントというより、フォントレンダリングソフトウェアの気持ちになってフォントを読んでみよう、という感じでごぜーますが。

フォントの準備

…………。
さて。

読むフォントは、容量が小さいとありがたいのですが、ちょっと探しただけでは見つからなかったので、Noto Sans RegularをFont Squirrel Webfont Generator使ってASCIIの小文字アルファベットだけにしたものを用意しました。だいたい9KB位。

用意したフォントはここからダウンロードできます: font.ttf


さて、適当なバイナリエディタでフォントを開き、16進ダンプを印刷します。
f:id:nixeneko:20161007234742j:plain
一応、印刷したPDFのリンクを張っておきます: font.pdf


……えーと、別に印刷する必要なかったですね。バイナリエディタで見ていきます。今回はStirlingを使いました。


今回はWindows用の情報をたどって、"cat"を出力することにしましょう。UnicodeでU+0063 U+0061 U+0074です。

あと、長くなってしまったので今回表示に関係ないところについてはデフォルトで表示されないようにしています。クリックで展開できるので興味があれば開いてください。

OpenType Font File

OpenType specification - Typography | Microsoft Docs
このOpenTypeの仕様をみながら読んでいきます。

特に、OpenTypeフォントファイルの仕様についての次のページを参考にします。
OpenType font file - Typography | Microsoft Docs
最初にデータ型について書かれています。
特に説明を要するものもなさそうですが、固定小数点数を10進数に直すには、整数として読み出し、2小数部分のビット数で割ると実際の値になるようです。


さて、初めから読んでいきます。OpenTypeフォーマットはバイトオーダがビッグエンディアンなので直感的ですね。
最初に来るのが0x00010000だから(TTC Headerでなく)Offset Tableなので、TTCでない単一のフォントでかつTrueTypeアウトラインのものだとわかります。

Offset Table

Type Name Value 備考
ULONG sfntVersion 0x00010000 TrueType Outline
USHORT numTables 0x0013 19
USHORT searchRange 0x0100 256
USHORT entrySelector 0x0004 4
USHORT rangeShift 0x0030 48

numTablesはテーブルの数で19個あるとわかり、そのあとの3つはnumTablesから計算される数です。
entrySelectorは  2^n \leq \text{numTables} なる最大の整数 nで、他は  \text{searchRange} = 16\cdot2^\text{entrySelector},  \text{rangeShift} = 16\cdot\text{numTables} - \text{searchRange} だそうです。計算してみると上の表のとおりになることが分かります。

Table Record

Type Name Description
ULONG tag テーブルを特定するための4文字の識別子
ULONG checkSum テーブルのチェックサム
ULONG offset テーブルのフォントファイルの先頭からのオフセット
ULONG length テーブルの長さ

直後にくるのがTable Recordで、これがOffset TableのnumTables……つまり19個並んでいるはずです。読みだすと次のようになっていました。
チェックサムの確認は面倒なのでしませんでしたが、offsetとlengthから計算できる値で計算方法は仕様を参照。

tag checkSum offset length
'FFTM' 0x61D6EAF7 0x013C (316) 0x001C (28)
'GDEF' 0x005C0024 0x0158 (344) 0x0028 (40)
'GPOS' 0xA176C2E4 0x0180 (384) 0x00D0 (208)
'GSUB' 0x9966BC6F 0x0250 (592) 0x0076 (118)
'OS/2' 0x79FC780F 0x02C8 (712) 0x0060 (96)
'cmap' 0xD6E8B4E8 0x0328 (808) 0x0182 (386)
'cvt ' 0x0F5312F5 0x04AC (1196) 0x0048 (72)
'fpgm' 0x53B42FA7 0x04F4 (1268) 0x0265 (613)
'gasp' 0x00160023 0x075C (1884) 0x0010 (16)
'glyf' 0xF470EA57 0x076C (1900) 0x1164 (4452)
'head' 0x0D957EF6 0x18D0 (6352) 0x0036 (54)
'hhea' 0x0EF404F9 0x1908 (6408) 0x0024 (36)
'hmtx' 0x9AC30ABF 0x192C (6444) 0x00B4 (180)
'loca' 0x7CD081CC 0x19E0 (6624) 0x005C (92)
'maxp' 0x014700FD 0x1A3C (6716) 0x0020 (32)
'name' 0x8B41D5BC 0x1A5C (6748) 0x06A4 (1700)
'post' 0xE1EBCFD6 0x2100 (8448) 0x0103 (259)
'prep' 0x7730038F 0x2204 (8708) 0x015A (346)
'webf' 0x62B657D9 0x2360 (9056) 0x0006 (6)

必ずしもあるテーブルについのoffset+lengthが次のテーブルのoffsetとなっていないのは、テーブルが4バイト境界にアラインメントされるためで、lengthには実際のテーブルの長さが記録されるのだそうです。実際のテーブルから次の4バイト境界までの隙間は0で埋められます。

'FFTM'はFontForge Time StampでFontForgeが出力するデータ、'webf'は不明ですがおそらくWebfontの略か、それぞれ独自テーブルなので無視します。

さて、OpenType tableのリストとその位置が得られたので、見ていきます。
どういう順番で見てくのがいいんでしょうか。とりあえずheadから行きます。

'head' - Font Header Table

フォントヘッダだそうです。開始オフセットが0x18D0なのでそこから見ていきます。54バイトなので短い。

Type Name Value 備考
USHORT majorVersion 0x0001 1固定
USHORT minorVersion 0x0000 0固定
Fixed fontRevision 0x00010F5C 1.06くらい
ULONG checkSumAdjustment 0xEC47630E
ULONG magicNumber 0x5F0F3CF5 0x5F0F3CF5固定
USHORT flags 0x001F ベースラインがy=0;
Left sidebearing point at x=0;
Instructions may depend on point size;
Force ppem to integer values for all internal scaler math;
Instructions may alter advance width
USHORT unitsPerEm 0x0800 2048
LONGDATETIME created 0x00000000D3FF1335 2016-09-14T14:46:13+00:00
mac時間らしい
LONGDATETIME modified 0x00000000D3FF1335 2016-09-14T14:46:13+00:00
SHORT xMin 0xFF8F -113 バウンディングボックスのx最小値
SHORT yMin 0xFE14 -492 同y最小値
SHORT xMax 0x06D5 1749 同x最大値
SHORT yMax 0x061F 1567 同y最大値
USHORT macStyle 0x0000 なし
USHORT lowestRecPPEM 0x0008 最小可読サイズ。8px
SHORT fontDirectionHint 0x0002 2固定。廃止
SHORT indexToLocFormat 0x0000 short offsets
SHORT glyphDataFormat 0x0000 0固定

'OS/2' - OS/2 and Windows Metrics Table

offsetが0x02C8なのでそこから読みます。
OS/2 - OS/2 and Windows metrics table specification - Typography | Microsoft Docs (Version 5)
実質Windows専用なんですかね。
はじめ2バイトのversionが0x0004であるからこれがVersion 4のOS/2テーブルということが分かります。
OS/2 - OS/2 and Windows metrics table specification (version 4) - Typography | Microsoft Docs (Version 4)

Type Name of Entry Value comment
USHORT version 0x0004 4
SHORT xAvgCharWidth 0x0387 903; 幅が0でないグリフの幅の算術平均
USHORT usWeightClass 0x0190 400 = Normal (Regular)
USHORT usWidthClass 0x0005 5 = Medium (normal)
USHORT fsType 0x0004 4 = 表示・印刷用にフォント埋め込み可(編集不可)
SHORT ySubscriptXSize 0x059A 1434; 下付き文字の大きさの目安
SHORT ySubscriptYSize 0x0533 1331
SHORT ySubscriptXOffset 0x0000 0; 下付き文字の位置の目安
SHORT ySubscriptYOffset 0x011F 287
SHORT ySuperscriptXSize 0x059A 1434; 上付き文字の大きさの目安
SHORT ySuperscriptYSize 0x0533 1331
SHORT ySuperscriptXOffset 0x0000 0; 上付き文字の位置の目安
SHORT ySuperscriptYOffset 0x03D1 977
SHORT yStrikeoutSize 0x0066 102; 打消線の幅
SHORT yStrikeoutPosition 0x0200 512; 打消線の上側の位置
SHORT sFamilyClass 0x0802 2050
BYTE panose[10] 0x020B0502040504020204
ULONG ulUnicodeRange1 0xE00002FF Basic Latin, Latin-1 Supplement, Latin Extended-A, Latin Extended-B, IPA Extensions, Phonetic Extensions, Phonetic Extensions Supplement, Spacing Modifier Letters, Modifier Tone Letters, Combining Diacritical Marks, Combining Diacritical Marks Supplement, Greek and Coptic, Cyrillic, Cyrillic Supplement, Cyrillic Extended-A, Cyrillic Extended-B, Latin Extended Additional, Latin Extended-C, Latin Extended-D, Greek Extended, General Punctuation, Supplemental Punctuation
ULONG ulUnicodeRange2 0x00000000
ULONG ulUnicodeRange3 0x00000000
ULONG ulUnicodeRange4 0x00000000
CHAR achVendID[4] 0x474F4F47 'GOOG'; 大文字IDはMicrosoftに登録しないと使えないらしい
USHORT fsSelection 0x0040 REGULAR
USHORT usFirstCharIndex 0x000D CR…フォント内の最小のUnicode index
USHORT usLastCharIndex 0x25FC '◼'…フォント内の最大のUnicode index
SHORT sTypoAscender 0x088D 2189; typographic ascender; 'hhea'のAscenderとは異なる
SHORT sTypoDescender 0xFDA8 -600; typographic descender; 'hhea'のDescenderとは異なる
SHORT sTypoLineGap 0x0000 0; typographic line gap; 'hhea'のLineGapとは異なる
USHORT usWinAscent 0x088D 2189; ascender metric for Windows
USHORT usWinDescent 0x0258 600; descender metric for Windows
ULONG ulCodePageRange1 0x2000019F サポートするコードページ: Latin 1, Latin 2: Eastern Europe, Cyrillic, Greek, Turkish, Windows Baltic, Vietnamese, Macintosh Character Set (US Roman),
ULONG ulCodePageRange2 0xDFD70000 IBM Greek, MS-DOS Russian, MS-DOS Nordic, MS-DOS Canadian French, MS-DOS Icelandic, MS-DOS Portuguese, IBM Turkish, IBM Cyrillic; primarily Russian, Latin 2, MS-DOS Baltic, Greek; former 437 G, WE/Latin 1
SHORT sxHeight 0x044A 1098; エックスハイト
SHORT sCapHeight 0x0000 0; キャップハイトあるいは大文字Hの高さ
USHORT usDefaultChar 0x0000 0
USHORT usBreakChar 0x0020 ' ' スペース
USHORT usMaxContext 0x0002 2 = ペアカーニングや2文字までを対象したフィーチャしかない

Windows用のソフトでは、sTypoAscender, sTypoDescender, sTypoLineGapを利用して標準のline-spaceingを計算するのが推奨されてるらしいです。

'hhea' - Horizontal Header Table

offsetが0x1908なのでそこから読んでいきます。
hhea - Horizontal header table specification - Typography | Microsoft Docs

Type Name of Entry Value comment
USHORT majorVersion 0x0001 1固定
USHORT minorVersion 0x0000 0固定
FWORD Ascender 0x088D 2189; Typographic ascent
FWORD Descender 0xFDA8 -600; Typographic descent
FWORD LineGap 0x0000 0; Typographic line gap
UFWORD advanceWidthMax 0x077B 1915; Maximum advance width value in 'hmtx'
FWORD minLeftSideBearing 0xFF8F -113; Minimum left sidebearing value in 'hmtx'
FWORD minRightSideBearing 0xFFA8 -88
FWORD xMaxExtent 0x06D5 1749; Max(lsb + (xMax - xMin))
SHORT caretSlopeRise 0x0001 1 for vertical
SHORT caretSlopeRun 0x0000 0 for vertical
SHORT caretOffset 0x0000 0 for non-slanted fonts
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT metricDataFormat 0x0000 0固定
USHORT numberOfHMetrics 0x002D 45; 'hmtx'におけるhMetricエントリの数

ascender, descender, linegapはMac用。とはいえ'OS/2'にあるものと一致してますね。

'maxp' - Maximum Profile

offsetが0x1A3Cなのでそこから読みます。
maxp - Maximum Profile table specification - Typography | Microsoft Docs
最初が0x00010000なのでversion 1.0であることがわかります。それに従って読んでいくと……

Type Name value comment
Fixed Table version number 0x00010000 version 1.0
USHORT numGlyphs 0x002D 45; グリフの数
USHORT maxPoints 0x0029 41; Maximum points in a non-composite glyph.
USHORT maxContours 0x0002 2; Maximum contours in a non-composite glyph.
USHORT maxCompositePoints 0x0000 0; Maximum points in a composite glyph.
USHORT maxCompositeContours 0x0000 0; Maximum contours in a composite glyph.
USHORT maxZones 0x0002 instructions use the twilight zone (Z0); 大体は2
USHORT maxTwilightPoints 0x0001 1; Maximum points used in Z0.
USHORT maxStorage 0x0002 2; Number of Storage Area locations.
USHORT maxFunctionDefs 0x0016 22; Number of FDEFs
USHORT maxInstructionDefs 0x0000 0; Number of IDEFs.
USHORT maxStackElements 0x0100 256; Maximum stack depth.
USHORT maxSizeOfInstructions 0x00D0 208; Maximum byte count for glyph instructions.
USHORT maxComponentElements 0x0000 0; Maximum number of components referenced at “top level” for any composite glyph.
USHORT maxComponentDepth 0x0000 0; Maximum levels of recursion

'cmap' - Character To Glyph Index Mapping Table

offsetが0x0328からなのでそこから読みます。
cmap - Character To Glyph Index Mapping Table - Typography | Microsoft Docs
文字コードを実際のグリフの内部コード(Glyph IDもしくはGID)にマッピングするテーブルです。

cmap Header

Type Name value comment
USHORT version 0x0000 0
USHORT numTables 0x0003 3; エンコーディングテーブルの数

Encoding Record

つぎに来るのがencoding recordの列で、cmapヘッダから3つあることがわかります。レコードの形式は次の様になっています。

Type Name 備考
USHORT platformID
USHORT encodingID
ULONG offset サブテーブルへのオフセット

platformIDやencodingIDは'name'テーブルのものと同じらしい。さらにサブテーブルがあるようですね。

これに従って読んでいくと……

platformID encodingID offset
0x0000 (Unicode) 0x0003 (Unicode 2.0 and onwards semantics, Unicode BMP only) 0x0000001C
0x0001 (Macintosh) 0x0000 (Roman) 0x0000007C
0x0003 (Windows) 0x0001 (Unicode BMP (UCS-2)) 0x0000001C

よく見てみると、UnicodeWindowsのレコードのoffsetが同一です。サブテーブルは実際には2つだけ含まれているということでしょう。

Unicode, Windows用サブテーブル

0x0328 + 0x001C = 0x0344 から読みます。というか前と連続してますね。

先頭が0x0004なのでFormat 4サブテーブルのようです。

Type Name value 備考
USHORT format 0x0004 Format number 4
USHORT length 0x0060 96; このsubtableの長さ
USHORT language 0x0000 Mac用以外では0
USHORT segCountX2 0x0014 20; 2×segCount= 20 よって segCount=10
USHORT searchRange 0x0010 16; segCount以上で最小の2の冪乗数
USHORT entrySelector 0x0003 3; log2(searchRange/2)
USHORT rangeShift 0x0004 4; 2×segCount - searchRange = 20 - 16 = 4
USHORT endCount[segCount] 0x0000 End characterCode for each segment
0x000D
0x0020
0x007A
0x00A0
0x200A
0x202F
0x205F
0x25FC
0xFFFF last=0xFFFF
USHORT reservedPad 0x0000 0固定
USHORT startCount[segCount] 0x0000 Start character code for each segment
0x000D
0x0020
0x0061
0x00A0
0x2000
0x202F
0x205F
0x25FC
0xFFFF
SHORT idDelta[segCount] 0x0001 Delta for all character codes in segment
0xFFF5
0xFFE3
0xFFA3
0xFF7E
0xE01F
0xDFFB
0xDFCC
0xDA30
0x0001
USHORT idRangeOffset[segCount] 0x0000 0; Offsets into glyphIdArray or 0
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
USHORT glyphIdArray[ ] なし

segCountが分からないとendCount以降が読み込めないので、先にsegCountを読んで配列の個数を確定させてから読んでいく必要があります。
さて、このformat 4は文字コードとglyph indexが連続して対応してる場合にコンパクトに記録できる方式のようです。

まとめます。

index endCount startCount idDelta idRangeOffset
0 0x0000 0x0000 0x0001 (1) 0x0000
1 0x000D 0x000D 0xFFF5 (-11) 0x0000
2 0x0020 0x0020 0xFFE3 (-29) 0x0000
3 0x007A 0x0061 0xFFA3 (-93) 0x0000
4 0x00A0 0x00A0 0xFF7E (-130) 0x0000
5 0x200A 0x2000 0xE01F (-8161) 0x0000
6 0x202F 0x202F 0xDFFB (-8197) 0x0000
7 0x205F 0x205F 0xDFCC (-8244) 0x0000
8 0x25FC 0x25FC 0xDA30 (-9680) 0x0000
9 0xFFFF 0xFFFF 0x0001 (1) 0x0000

さて、Unicodeスカラ値とグリフの対応を見てみましょう。今回欲しいのは“cat”(0x0063, 0x0061, 0x0074)なので、それに対応するものを探しましょう。
これらはendCount順にソートされています。endCountを順に見ていくと、4番目(index = 3)の0x007Aで初めてc (0x0063), a (0x0061), t (0x0074)の値以上になります。
そのためそれに対応するstartCountを見ると0x0061となっていて、c, a, tはいずれもそれ以上であり、したがってマッピングが定義されていることが分かります。
そこで、対応するidDeltaとidRangeOffset (ここでは0なので気にしない)からGlyph IDを計算します。ここでは、文字コードにidDelta = -93を加えると(=93を引くと)求めるGlyph IDになります。

文字 文字コード Glyph ID
c 0x0063 6
a 0x0061 4
t 0x0074 23

Macintosh用subtable

0x0328 + 0x007C = 0x03A4 から。

'hmtx' - Horizontal Metrics

offsetが0x192Cなのでそこから読みます。
hmtx - Horizontal metrix table specification - Typography | Microsoft Docs
グリフ幅と左サイドベアリングが記録されているようです。

Field Type
hMetrics longHorMetric[numberOfHMetrics]
leftSideBearing SHORT[ ]

'hhea'のnumberOfHMetricsが45であることから、45個hMetricsデータが連なってるはずです。

という訳で順番に読んでいきます。なおlongHorMetricは

  • USHORT advanceWidth;
  • SHORT lsb;

からなる構造体です。(lsbは左サイドベアリング)

hMetrics

index advanceWidth lsb
0 0x02EC 0x0044
1 0x0000 0x0000
2 0x0414 0x0000
3 0x0214 0x0000
4 0x047D 0x005E
5 0x04EC 0x00AE
6 0x03D7 0x0071
7 0x04EC 0x0071
8 0x0483 0x0071
9 0x02C1 0x001F
10 0x04EC 0x0071
11 0x04F2 0x00AE
12 0x0210 0x00A0
13 0x0210 0xFF8F
14 0x0446 0x00AE
15 0x0210 0x00AE
16 0x077B 0x00AE
17 0x04F2 0x00AE
18 0x04D7 0x0071
19 0x04EC 0x00AE
20 0x04EC 0x0071
21 0x034E 0x00AE
22 0x03D5 0x0068
23 0x02E3 0x0021
24 0x04F2 0x00A2
25 0x0410 0x0000
26 0x064A 0x0017
27 0x043B 0x0025
28 0x0414 0x0002
29 0x03C3 0x0050
30 0x0214 0x0000
31 0x030F 0x0000
32 0x061F 0x0000
33 0x030F 0x0000
34 0x061F 0x0000
35 0x020A 0x0000
36 0x0187 0x0000
37 0x0105 0x0000
38 0x0105 0x0000
39 0x00C3 0x0000
40 0x0139 0x0000
41 0x0057 0x0000
42 0x0139 0x0000
43 0x0187 0x0000
44 0x044C 0x0000

あれ、これでこのテーブル終わりのようです。leftSideBearingフィールドの入る余地がないですね。
leftSideBearingの個数は'maxp'のnumGlyphsを使って numGlyphs - numberOfHMetrics で計算されるらしいので、 45 - 45 = 0 なのであってます。主に等幅のグリフのためのものだとか。

"cat"の3文字についてまとめます。

文字 Glyph ID advanceWidth lsb
c 6 0x03D7 (983) 0x0071 (113)
a 4 0x047D (1149) 0x005E (94)
t 23 0x02E3 (739) 0x0021 (33)

'name' - Naming Table

offsetは0x1A5Cなのでそこから読んでいきます。
name - Naming table specification - Typography | Microsoft Docs
グリフを得るのには特に必要なさそうです。

'post' - PostScript Table

offsetは0x2100なのでそこから読みます。
post — PostScript Table specification - Typography | Microsoft Docs
PostScriptプリンタのために必要な情報を記録するところとのことで、グリフ名とかが格納されているようです。

'GDEF'

offsetは0x0158。
GDEF — Glyph Definition Table - Typography | Microsoft Docs
グリフの属するクラスを定義したり、リガチャのグリフのキャレット位置を指定したりするテーブルの様です。

'GPOS'

offsetは0x0180
GPOS — Glyph Positioning Table - Typography | Microsoft Docs
カーニングの調整、合成記号がどこにつくかの指定など、条件つきのグリフの位置調整を指定するテーブルです。

'GSUB'

offsetは0x0250。
GSUB — Glyph Substitution Table - Typography | Microsoft Docs
合字や、アラビア文字などにおける独立・語頭・語中・語末形の切り替えなど、特定の条件でグリフを置き換えることを指定するテーブルです。

'gasp' — Grid-fitting And Scan-conversion Procedure Table

offsetは0x075C。
gasp — Grid-fitting And Scan-conversion Procedure Table - Typography | Microsoft Docs
PPEMサイズの違いによりラスタライザの描画品質を向上させるためのフラグ設定等が書かれているようです。

'cvt ' - Control Value Table

offsetは0x04AC。
cvt - Control value table - Typography | Microsoft Docs

TrueType instructionから参照される値がつまってるようです。

FWORDの値が並んでいます。

0 1 2 3 4 5 6 7 8 9
0x 0x0000 0x044A 0x05B6 0x00A2 0x00E5 0x006A 0x007D 0x008C 0x0091 0x0095
1x 0x009A 0x009D 0x0054 0x00B4 0x00D3 0x0081 0x008F 0x0094 0x00AA 0x00AE
2x 0x00B4 0x00B8 0x00BE 0x00C2 0x006B 0x00A6 0x00BA 0x0084 0x00B2 0x008A
3x 0x0077 0x007B 0x00AC 0x00B0 0x0044 0x0511

'fpgm' - Font Program

offsetは0x04F4。
fpgm - Font program table - Typography | Microsoft Docs
BYTE[ n ]
最初に一回だけ実行されるプログラムだそうで、TrueType命令によって関数定義を行っているようです。

また、TrueType命令については、最近のMac OSからは単純に無視される様です。高DPI環境には不要ということでしょうか。今回描画に必要でないので省略します。

'prep' - Control Value Program

offsetは0x2204。
prep - Control value program table - Typography | Microsoft Docs

フォントサイズや変換行列などが変わる度に呼び出されるプログラムらしいです。グリフ固有の処理はできません。TrueType命令が並んでいます。

'loca' - Index to Location

offsetは0x19E0。
loca - Index-to-location - Typography | Microsoft Docs

このテーブルには2種類の形式がありますが、headテーブルのindexToLocFormat = 0であることからshort版であることがわかります。

indexToLoc table short version

Type Name 説明
USHORT offsets[n] 2で割ったローカルオフセット
n = numGlyphs('maxp') + 1

maxpテーブルのnumGlyphs = 45なので、USHORT型が46個並んだ配列となっているはずです。

index 0 1 2 3 4 5 6 7
0o00 0x0000 0x002C 0x002C 0x002C 0x002C 0x00A4 0x010E 0x0154
0o10 0x01BA 0x021E 0x0272 0x02EC 0x033C 0x0376 0x03C2 0x03FE
0o20 0x0418 0x0482 0x04CC 0x051A 0x0580 0x05E6 0x0622 0x0688
0o30 0x06EC 0x073A 0x0766 0x0806 0x0838 0x0878 0x08A6 0x08A6
0o40 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6
0o50 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08B2

'glyf' - Glyf Data

offsetは0x076C。
glyf - Glyf data table - Typography | Microsoft Docs

これにはグリフのデータが並んでいます。実際に描画される文字図形の本体ですね。フォントファイルの大部分を占めています。
locaテーブルに書かれているGlyphIDに対応するオフセットにアクセスするとそのグリフが得られるようです。

さすがに全部順番に読んでいくのは無理なので、今回描こうとしている“cat”の三文字について見ていきましょうか。

cmapテーブルから、c, a, tのGlyph IDはそれぞれ6, 4, 23であることがわかります。locaテーブルから対応するオフセットを求めると……

文字 glyph ID offset アドレス
c 6 0x021C 0x0988 - 0x0A14
a 4 0x0058 0x07C4 - 0x08B4
t 23 0x0D10 0x147C - 0x1544

となるので、それぞれを見ていきましょう。

'c' 0x0988 - 0x0A14

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0001 1; >= 0 なのでsingle glyph
SHORT xMin 0x0071 Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x0393 Maximum x for coordinate data.
SHORT yMax 0x045E Maximum y for coordinate data.

numberOfCoutours >= 0 で単体のグリフなので、次のデータが来ます。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x0016 22; Array of last points of each contour; n = numberOfCoutours
USHORT instructionLength 0x003D 61; instructionsのバイト数
BYTE instructions[n] 以下 Array of instructions for each glyph; n is the number of instructions.
BYTE flags[n] 以下 Array of flags for each coordinate in outline; n is the number of flags.
BYTE or SHORT xCoordinates[ ] 以下 First coordinates relative to (0,0); others are relative to previous point.
BYTE or SHORT yCoordinates[ ] 以下 First coordinates relative to (0,0); others are relative to previous point.
TrueType命令 (0x0996~0x9D3)

制御点列とその座標

flagsの配列の大きさは最初から確定できないので順番に読んでいく必要があるようです。インデックスがendPtsOfContoursの最大の値に等しくなるまで読んでいきます。
flagには、x,yがそれぞれBYTE(さらに正か負)かSHORTか前の値と同じかについての情報が含まれているので、それに従ってまず各制御点のx座標を、次にy座標を読みだしていきます。記録されている座標の値は前の座標との差分になっていて、記録スペースを圧縮することができるようになっているようです。

1バイト正数を+1b, 1バイト負数を-1b, 2バイト整数を2b, 前と変化なしをsameとします。

idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +113 +543 113 543
1 0x10 (0b00010000) same 2b 0 +276 113 819
2 0x00 (0b00000000) 2b 2b +267 +299 380 1118
3 0x33 (0b00110011) +1b same +247 0 627 1118
4 0x32 (0b00110010) +1b same +80 0 707 1118
5 0x16 (0b00010110) +1b -1b +157 -33 864 1085
6 0x17 (0b00010111) +1b -1b +51 -26 915 1059
7 0x07 (0b00000111) -1b -1b -55 -150 860 909
8 0x26 (0b00100110) -1b +1b -139 +52 721 961
9 0x23 (0b00100011) -1b same -98 0 623 961
10 0x22 (0b00100010) -1b same -166 0 457 961
11 0x06 (0b00000110) -1b -1b -158 -209 299 752
12 0x15 (0b00010101) same -1b 0 -207 299 545
13 0x14 (0b00010100) same -1b 0 -199 299 346
14 0x16 (0b00010110) +1b -1b +158 -211 457 135
15 0x33 (0b00110011) +1b same +155 0 612 135
16 0x32 (0b00110010) +1b same +145 0 757 135
17 0x37 (0b00110111) +1b +1b +140 +64 897 199
18 0x15 (0b00010101) same -1b 0 -160 897 39
19 0x06 (0b00000110) -1b -1b -114 -59 783 -20
20 0x23 (0b00100011) -1b same -169 0 614 -20
21 0x22 (0b00100010) -1b same -237 0 377 -20
22 0x00 (0b00000000) 2b 2b -264 +291 113 271

座標の単位はFUNITです。

とりあえず制御点列が得られたので、プロットしていきましょう。簡
単のため、今回は10 FUNITを1mmとします。このフォントはEMが2048 FUNITなので、EMスクエアは204.8mmとなる計算です。
ここでは、黒い点がon-curve point、赤い×がoff-curve pointです。
f:id:nixeneko:20161001195629p:plain

それっぽいですね。

'a' 0x07C4 - 0x08B4

同様に読んでいきます。

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0002 2; >= 0 なのでsingle glyph
SHORT xMin 0x005E Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x03D7 Maximum x for coordinate data.
SHORT yMax 0x045C Maximum y for coordinate data.

numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x001A 26; Array of last points of each contour; n = numberOCoutours
0x0025 37
USHORT instructionLength 0x007B 123; instructionsのバイト数
BYTE instructions[n] 以下 TrueType命令
BYTE flags[n] 以下 このフラグに従って座標データを読む
BYTE or SHORT xCoordinates[ ] 以下 x座標データ差分
BYTE or SHORT yCoordinates[ ] 以下 y座標データ差分
TrueType命令: 0x07D4~0x084F

制御点列とその座標
idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +94 +305 94 305
1 0x10 (0b00010000) same 2b 0 +334 94 639
2 0x25 (0b00100101) 2b +1b +527 +16 621 655
3 0x37 (0b00110111) +1b +1b +186 +7 807 662
4 0x35 (0b00110101) same +1b 0 +65 807 727
5 0x34 (0b00110100) same +1b 0 +125 807 852
6 0x26 (0b00100110) -1b +1b -108 +119 699 971
7 0x23 (0b00100011) -1b same -119 0 580 971
8 0x22 (0b00100010) -1b same -87 0 493 971
9 0x06 (0b00000110) -1b -1b -155 -52 338 919
10 0x07 (0b00000111) -1b -1b -68 -32 270 887
11 0x27 (0b00100111) -1b +1b -55 +135 215 1022
12 0x3E (0b00111110) +1b +1b +83 +44 298 1066
13 0x01 +1b +1b +196 +50 494 1116
14 0x33 (0b00110011) +1b same +96 0 590 1116
15 0x32 (0b00110010) +1b same +199 0 789 1116
16 0x16 (0b00010110) +1b -1b +194 -176 983 940
17 0x15 (0b00010101) same -1b 0 -192 983 748
18 0x11 (0b00010001) same 2b 0 -748 983 0
19 0x23 (0b00100011) -1b same -131 0 852 0
20 0x27 (0b00100111) -1b +1b -35 +156 817 156
21 0x23 (0b00100011) -1b same -8 0 809 156
22 0x0E (0b00001110) -1b -1b -82 -103 727 53
23 0x01 -1b -1b -163 -73 564 -20
24 0x23 (0b00100011) -1b same -124 0 440 -20
25 0x22 (0b00100010) -1b same -162 0 278 -20
26 0x26 (0b00100110) -1b +1b -184 +170 94 150
27 0x37 (0b00110111) +1b +1b +187 +153 281 303
28 0x14 (0b00010100) same -1b 0 -86 281 217
29 0x16 (0b00010110) +1b -1b +105 -92 386 125
30 0x33 (0b00110011) +1b same +95 0 481 125
31 0x32 (0b00110010) +1b same +151 0 632 125
32 0x36 (0b00110110) +1b +1b +173 +163 805 288
33 0x3D (0b00111101) same +1b 0 +150 805 438
34 0x01 same +1b 0 +99 805 537
35 0x07 (0b00000111) -1b -1b -162 -7 643 530
36 0x0E (0b00001110) -1b -1b -189 -7 454 523
37 0x01 -1b -1b -173 106 281 417

制御点の0-26と27-37が別々のcontourになります。
cと同様にaの制御点列もプロットしていきましょう。'hmtx'で見たようにcは幅983なので、aは制御点のx座標に983を加えた場所にプロットします。
f:id:nixeneko:20161001201014p:plain

't' 0x147C - 0x1544

これも同様に読んでいきます。

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0001 1; >= 0 なのでsingle glyph
SHORT xMin 0x0021 Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x02B6 Maximum x for coordinate data.
SHORT yMax 0x0546 Maximum y for coordinate data.

numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x0016 22; Array of last points of each contour; n = numberOCoutours
USHORT instructionLength 0x007A 122; instructionsのバイト数
BYTE instructions[n] 以下 TrueType命令
BYTE flags[n] 以下 このフラグに従って座標データを読む
BYTE or SHORT xCoordinates[ ] 以下 x座標データ差分
BYTE or SHORT yCoordinates[ ] 以下 y座標データ差分
TrueType命令 (0x148A-0x1504)

制御点列とその座標
idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +33 +958 33 958
1 0x35 (0b00110101) same +1b 0 +86 33 1044
2 0x3F (0b00111111) +1b +1b +157 +72 190 1116
3 0x01 +1b +1b +72 +234 262 1350
4 0x33 (0b00110011) +1b same +107 0 369 1350
5 0x15 (0b00010101) same -1b 0 -252 369 1098
6 0x21 (0b00100001) 2b same +317 0 686 1098
7 0x15 (0b00010101) same -1b 0 -140 686 958
8 0x21 (0b00100001) 2b same -317 0 369 958
9 0x11 (0b00010001) same 2b 0 -634 369 324
10 0x14 (0b00010100) same -1b 0 -95 369 229
11 0x16 (0b00010110) +1b -1b +91 -102 460 127
12 0x33 (0b00110011) +1b same +81 0 541 127
13 0x32 (0b00110010) +1b same +35 0 576 127
14 0x36 (0b00110110) +1b +1b +94 +14 670 141
15 0x37 (0b00110111) +1b +1b +24 +9 694 150
16 0x15 (0b00010101) same -1b 0 -138 694 12
17 0x0E (0b00001110) -1b -1b -25 -11 669 1
18 0x01 -1b -1b -105 -21 564 -20
19 0x23 (0b00100011) -1b same -54 0 510 -20
20 0x20 (0b00100000) 2b same -322 0 188 -20
21 0x19 (0b00011001) same 2b 0 +339 188 319
22 0x01 same 2b 0 +639 188 958

また、tについてもプロットしていきましょう。cとaの幅 = 983 + 1149をx座標に加えた位置にプロットします。
f:id:nixeneko:20161001201414p:plain

アウトライン描画

さて、これら制御点列からアウトラインを描いていきましょう。
on-curve pointはその名前の通りグリフの輪郭(=contour)の上にある点です。最終的なアウトラインはon-curve pointを通ります。
一方でoff-curve pointは曲線の曲がり方を制御する点で、最終的なアウトラインは普通この点を通りません。


on-curve pointが2つ連続すると直線になります。連続するon-curve pointを黒線で結びます。
f:id:nixeneko:20161001201811p:plain
tは大体直線でできていますね。

次に、曲線を求めていかないといけないのですが、まず準備として制御点列を線で結びます。
f:id:nixeneko:20161001202006p:plain

そして、連続するoff-curve pointの中点にon-curve pointを打っていきます。
f:id:nixeneko:20161001202148p:plain
こうすることで、曲線の部分は2次ベジエ(Bézier)曲線の集まりとみなせます。


さて、2次ベジエを描いていきましょう。2次ベジエは3つの制御点からなる曲線で、両端の点を通り真ん中の点の方に近づくカーブを描きます。ここでは、on-curve・off-curve・on-curveの3点の並び(●-×-●の並び)が2次ベジエ曲線です。

今回は、ド・カステリョ(De Casteljau)のアルゴリズムの考え方によって、ベジエ曲線を半分半分に繰り返し分割していくことで求めて行きたいと思います。

まず、2次ベジエの隣り合う制御点の中点同士を結びます。
f:id:nixeneko:20161001203213p:plain
次にその中点を求めるとそれがon-curve pointとなります。
f:id:nixeneko:20161001203402p:plain

さて、分割されたベジエ曲線は次の図の●-+-●の並びになります。
f:id:nixeneko:20161001203553p:plain

間隔が十分に狭いところを除いて、さらに繰り返し分割していきます。
f:id:nixeneko:20161001204005p:plain
制御点を結んだ線がどんどん求める形に近づいていきますね。
もう一回分割します。
f:id:nixeneko:20161001204126p:plain
も、もういいかな……さらに分割してもいいですが面倒なのと狭くてうまく描けないのであとはフリーハンドで黒い点をなめらかに結んでいきます。
f:id:nixeneko:20161001204306p:plain
完成です! 良さそうですね! これをさらに、制御点を順番にたどっていったときの右手側を塗りつぶすとフォントのレンダリング結果になります。

さて、実際にPhotoshopでフォントを表示したものを重ねてみます。
f:id:nixeneko:20161001204527p:plain
いい感じに一致してますね!

感想

  • こうやって見ていくと自然に仕様書とにらめっこすることになり、フォントファイルについての理解が深まりますね。マゾい暇な人にお勧めします。
  • こんなことするより仕様に従ってフォントを読み込むソフトウェア作ったほうが生産性があるかもしれません。
  • データを格納する際に、容量が小さくなるような工夫が随所にあるのがわかります。オフセット指定によって内容が同一なものは複数記録する必要がなかったり、座標は差分のみを記録することで容量を節約したり、ランレングス圧縮的な要素もあります。
  • フォントヒンティングに関するTrueType命令の動作を把握することについては、フォントから制御点の座標を読み出す以上に面倒そうなので途中で投げました。詳しい人に動作を教わりたいです。
  • もうやらない。

逆引きTrueType instruction set

TrueType instruction setについて、バイナリからmnemonicが引けるようなものが見つからなかったので作った。

Code range Mnemonic Description
0x00 - 0x01 SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
0x02 - 0x03 SPVTCA[a] Set Projection_Vector To Coordinate Axis
0x04 - 0x05 SFVTCA[a] Set Freedom_Vector to Coordinate Axis
0x06 - 0x07 SPVTL[a] Set Projection_Vector To Line
0x08 - 0x09 SFVTL[a] Set Freedom_Vector To Line
0x0A SPVFS[ ] Set Projection_Vector From Stack
0x0B SFVFS[ ] Set Freedom_Vector From Stack
0x0C GPV[ ] Get Projection_Vector
0x0D GFV[ ] Get Freedom_Vector
0x0E SFVTPV[ ] Set Freedom_Vector To Projection Vector
0x0F ISECT[] moves point p to the InterSECTion of two lines
0x10 SRP0[ ] Set Reference Point 0
0x11 SRP1[ ] Set Reference Point 1
0x12 SRP2[ ] Set Reference Point 2
0x13 SZP0[ ] Set Zone Pointer 0
0x14 SZP1[ ] Set Zone Pointer 1
0x15 SZP2[ ] Set Zone Pointer 2
0x16 SZPS[ ] Set Zone PointerS
0x17 SLOOP[ ] Set LOOP variable
0x18 RTG[ ] Round To Grid
0x19 RTHG[ ] Round To Half Grid
0x1A SMD[ ] Set Minimum_ Distance
0x1B ELSE[] ELSE clause
0x1C JMPR[] JuMP Relative
0x1D SCVTCI[ ] Set Control Value Table Cut In
0x1E SSWCI[ ] Set Single_Width_Cut_In
0x1F SSW[ ] Set Single-width
0x20 DUP[] DUPlicate top stack element
0x21 POP[] POP top stack element
0x22 CLEAR[] CLEAR the stack
0x23 SWAP[] SWAP the top two elements on the stack
0x24 DEPTH[] DEPTH of the stack
0x25 CINDEX[] Copy the INDEXed element to the top of the stack
0x26 MINDEX[] Move the INDEXed element to the top of the stack
0x27 ALIGNPTS[] ALIGN Points
0x28
0x29 UTP[] UnTouch Point
0x2A LOOPCALL[] LOOP and CALL function
0x2B CALL[] CALL function
0x2C FDEF[] Function DEFinition
0x2D ENDF[] END Function definition
0x2E - 0x2F MDAP[a] Move Direct Absolute Point
0x30 - 0x31 IUP[a] Interpolate Untouched Points through the outline
0x32 - 0x33 SHP[a] SHift Point using reference point
0x34 - 0x35 SHC[a] SHift Contour using reference point
0x36 - 0x37 SHZ[a] SHift Zone using reference point
0x38 SHPIX[] SHift point by a PIXel amount
0x39 IP[] Interpolate Point
0x3A - 0x3B MSIRP[a] Move Stack Indirect Relative Point
0x3C ALIGNRP[] ALIGN to Reference Point
0x3D RTDG[ ] Round To Double Grid
0x3E - 0x3F MIAP[a] Move Indirect Absolute Point
0x40 NPUSHB[ ] PUSH N Bytes
0x41 NPUSHW[ ] PUSH N Words
0x42 WS[ ] Write Store
0x43 RS[ ] Read Store
0x44 WCVTP[ ] Write Control Value Table in Pixel units
0x45 RCVT[ ] Read Control Value Table
0x46 - 0x47 GC[a] Get Coordinate projected onto the projection_vector
0x48 SCFS[ ] Sets Coordinate From the Stack using projection_vector and freedom_vector
0x49 - 0x4A MD[a] Measure Distance
0x4B MPPEM[ ] Measure Pixels Per EM
0x4C MPS[ ] Measure Point Size
0x4D FLIPON[ ] Set the auto_flip Boolean to ON
0x4E FLIPOFF[ ] Set the auto_flip Boolean to OFF
0x4F DEBUG[] DEBUG call
0x50 LT[] Less Than
0x51 LTEQ[] Less Than or EQual
0x52 GT[] Greater Than
0x53 GTEQ[] Greater Than or Equal
0x54 EQ[] Equal
0x55 NEQ[] Not EQual
0x56 ODD[] ODD
0x57 EVEN[] EVEN
0x58 IF[] IF test
0x59 EIF[] End IF
0x5A AND[] logical AND
0x5B OR[] logical OR
0x5C NOT[] logical NOT
0x5D DELTAP1[] DELTA exception P1
0x5E SDB[ ] Set Delta_Base in the graphics state
0x5F SDS[ ] Set Delta_Shift in the graphics state
0x60 ADD[] ADD
0x61 SUB[] SUBtract
0x62 DIV[] DIVide
0x63 MUL[] MULtiply
0x64 ABS[] ABSolute value
0x65 NEG[] NEGate
0x66 FLOOR[] FLOOR
0x67 CEILING[] CEILING
0x68 - 0x6B ROUND[ab] ROUND value
0x6C - 0x6F NROUND[ab] No ROUNDing of value
0x70 WCVTF[ ] Write Control Value Table in Funits
0x71 DELTAP2[] DELTA exception P2
0x72 DELTAP3[] DELTA exception P3
0x73 DELTAC1[] DELTA exception C1
0x74 DELTAC2[] DELTA exception C2
0x75 DELTAC3[] DELTA exception C3
0x76 SROUND[ ] Super ROUND
0x77 S45ROUND[ ] Super ROUND 45 degrees
0x78 JROT[] Jump Relative On True
0x79 JROF[] Jump Relative On False
0x7A ROFF[ ] Round OFF
0x7B
0x7C RUTG[ ] Round Up To Grid
0x7D RDTG[ ] Round Down To Grid
0x7E SANGW[ ] Set Angle _Weight
0x7F AA[] Adjust Angle
0x80 FLIPPT[] FLIP PoinT
0x81 FLIPRGON[] FLIP RanGe ON
0x82 FLIPRGOFF[] FLIP RanGe OFF
0x83 - 0x84
0x85 SCANCTRL[ ] SCAN conversion ConTRoL
0x86 - 0x87 SDPVTL[a] Set Dual Projection_Vector To Line
0x88 GETINFO[] GET INFOrmation
0x89 IDEF[] Instruction DEFinition
0x8A ROLL[] ROLL the top three stack elements
0x8B MAX[] MAXimum of top two stack elements
0x8C MIN[] MINimum of top two stack elements
0x8D SCANTYPE[ ] SCANTYPE
0x8E INSTCTRL[] INSTRuction execution ConTRoL
0x8F - 0x90
0x91 GETVARIATION[ ] GET VARIATION
0x92 - 0xAF
0xB0 - 0xB7 PUSHB[abc] PUSH Bytes
0xB8 - 0xBF PUSHW[abc] PUSH Words
0xC0 - 0xDF MDRP[abcde] Move Direct Relative Point
0xE0 - 0xFF MIRP[abcde] Move Indirect Relative Point

0x28, 0x7B, 0x83 - 0x84, 0x8F - 0xAF には何が当てられているのだろうか?

(2016-09-20 追記)


(追記終)