TrueType命令で遊ぶシリーズ第2弾。第1弾は次を参照:
TrueType命令を利用して、フォントサイズに応じた角度だけ回転するようなフォントを作った。
完成品
コンピュータの世界広がりすぎ。
TrueType Instructionのコード
最終的に実装したコードは次のようになった。なおFontforgeで読み込める記述となっている(というより、Fontforgeによる逆アセンブル結果と言った方がいいか)。
'fpgm': 関数定義
PUSHB_1 0 FDEF PUSHB_1 2 ADD SVTCA[x-axis] GC[orig] PUSHB_1 128 DIV PUSHB_1 0 SWAP WS PUSHB_1 1 RCVT PUSHB_1 1 SWAP WS ENDF PUSHB_1 1 FDEF DUP ROLL DUP ROLL PUSHB_1 3 RS MUL SWAP PUSHB_1 2 RS MUL SUB PUSHW_1 4096 DIV ROLL ROLL PUSHB_1 3 RS MUL SWAP PUSHB_1 2 RS MUL ADD PUSHW_1 4096 DIV SWAP ENDF PUSHB_1 2 FDEF DUP DUP SVTCA[x-axis] GC[cur] SWAP SVTCA[y-axis] GC[cur] PUSHB_1 1 RS SUB SWAP PUSHB_1 0 RS SUB MPS PUSHB_1 1 LOOPCALL PUSHB_1 0 RS ADD SWAP PUSHB_1 1 RS ADD SVTCA[y-axis] PUSHB_1 3 CINDEX SWAP SCFS SVTCA[x-axis] SCFS POP ENDF PUSHB_1 3 FDEF DUP PUSHB_1 0 CALL DUP DUP PUSHB_1 0 GTEQ IF PUSHB_1 2 CINDEX PUSHB_1 2 CINDEX PUSHB_1 2 CALL PUSHB_1 1 SUB PUSHB_1 20 NEG JMPR EIF POP POP ENDF
'prep': 定数のStorage Areaへの格納
PUSHB_2 2 64 PUSHW_1 4096 MUL PUSHW_1 512 DIV WS PUSHW_3 3 8128 4096 MUL PUSHW_1 8192 DIV WS
'cvt ': Control Value Table
index | val |
0 | 0 |
1 | 379 |
cvtテーブルは事前にFunit(フォントエディタで図形を定義している時の座標)で数値を記述しておき、RCVT[ ]で読みだすときにはフォントサイズ(PPEM)に応じたピクセル数となって読みだされる。書きこむ際には書きこむ単位によってWCVTF[ ] (Funit), WCVTP[ ] (Pixel unit)の2命令が使える。
0番は0にしておいたが、実際には使わなかった。
1番は回転中心のy座標である。
グリフ固有のTrueType Instruction('glyf'内)
PUSHB_2 11 3 CALL
11の部分にはそのグリフの最大の制御点番号が入る。全部のグリフへの適用については前回の記事を参照。
以下、どのように実装をおこなったかを説明する。
方針
グリフを回転させることを考える。回転中心はグリフの真ん中あたりとし、その座標をとする。を中心に回転させるには、次のような手順をとる。
- 回転中心が原点に重なるよう平行移動
- (原点を中心に)回転
- 原点に移動していた回転中心が元の座標となるよう平行移動
制御点に対する座標を計算する。
1. 回転中心が原点に重なるよう平行移動
制御点の座標か回転中心の座標を引く。
2. (原点を中心に)回転
これは回転行列を掛けて
みたいな感じではあるが、に対するを計算するのが面倒だったので、固定した角度に対応する定数の値を用いて、
のように回転行列をフォントサイズに応じた回数適用することで、サイズに応じた回転を計算する。かなり効率が悪いのでどうにかしたいところではあるが……。*1
3. 原点に移動していた回転中心が元の座標となるよう平行移動
これは1.の逆である。
回転後の座標が計算できたので、制御点をそこに移動する。これをグリフに含まれる全ての制御点について繰り返せば、グリフの回転が計算できるという訳である。
実装
さて、より実装に近い部分について。
Storage Areaへの座標の割り付け
index | 割り付け |
---|---|
0 | (回転中心x座標) |
1 | (回転中心y座標) |
2 | |
3 |
Storage Areaにはこんな感じで割り付けを行い、一時的な変数として利用したり、あるいは定数を格納して使ったりしている。
回転中心の座標の取得
回転中心の取得は、y座標についてはグリフに依らず事前に値を決め打ちし、'cvt 'テーブルに1番地に座標を(Funitで)書き込んでおく。
x座標についてはphantom pointによって行う。phantom pointとは、グリフの幅や高さを取得(したり、制御)するためにラスタライザが自動で付加する制御点であり、取り扱うグリフにn個の制御点がある場合、がphantom pointとなる。次に図次するように、がx位置左端、が右端、が上端、が下端を示す。
なお、は古いバージョン(Microsoft rasterizer v.1.7より前)では使えないので、GETINFOでバージョンを取得し、MS rasterizerのバージョンが1.7以上であることを確認してから使わないといけないそうである。
Phantom Pointsについて、詳しくは、https://www.microsoft.com/typography/otspec/ttinst.htmのInstructing TrueType Glyphsを参照。
今回はグリフ幅の中心を求めるので、についてを計算するようにする。は原点にくるので特に値を参照しなくても幅の中心が求まる。
回転中心の座標を取得し、Storage Areaに書き込むコードを考える。
回転中心のx座標
x座標についてはphantom point の座標、すなわちグリフの幅を取得し、それを2で割ることによって中心を得る。
グリフAに対しては次のようになる。最初にpushしている11がグリフの最大の制御点番号n-1である。
PUSHB_1 11 PUSHB_1 2 ADD SVTCA[x-axis] GC[orig] PUSHB_1 128 DIV PUSHB_1 0 SWAP WS
GC
座標の取得は命令GCを使う。リファレンスによると、制御点番号p(整数)をpopし、その番号に対応する座標cをpushする。pushされる座標は描画するフォントサイズ(PPEM)におけるピクセル単位で表され、F26Dot6つまり小数点以下6bitの固定小数点数で表現される。
Pops | p: point number (uint32) |
Pushes | c: coordinate location (F26Dot6) |
GCには2種類あり、
である。これらは点を移動した場合などに得られる値が変わってくる。
x座標, y座標それぞれについて座標を取得する場合はそれぞれSVTCA[1], SVTCA[0]を事前に実行しておく。
なお、ここでF26Dot6は小数点以下6bitの固定小数点数である。これは実質、小数点数に64をかけた整数であり、F26Dot6を整数として評価した場合に64で割ると実際の数値が得られる。
例えば1.0は整数として評価すると64であり、2.0は128となる。
DIV
割り算命令DIVは、リファレンスによると
Pops | n2: divisor (F26Dot6) n1: dividend (F26Dot6) |
Pushes | (n1 * 64)/n2: quotient (F26Dot6) |
となっていて、F26Dot6に対して計算が定義されている。
スタックが
… | n1 | n2 |
となっているとき、n1, n2を整数として考えると(n1 * 64)/n2が計算される。
… | n1/n2(F26Dot6) |
n1, n2はF26Dot6で与えなければいけないため、2で割るためにはn2の部分が128になる。
従って、上のコードはGC[1]で取得したx座標を2.0(128)で割っている。
WS
最終的にWS命令でStorage Areaの0番に計算した値を保存する。
Pops | v: storage area value (uint32) l: storage area location (uint32) |
before … | l | v | after … |
Storage Areaのインデックスiに値vを書きこむ。
SWAP
なお、命令が要求するようにスタックの順番を合わせるために、SWAP命令を使っている。SWAP命令はトップ2項目の順番を入れ替える。
before: … | e1 | e2 | after: … | e2 | e1 |
スタックの入れ替えには他にもROLL(上から3番目の項目を先頭に)やMINDEX(指定したnについて、上からn番目の項目を先頭に)が使える。
回転中心のy座標
次に回転中心のy座標の取得をする。
PUSHB_1 1 RCVT PUSHB_1 1 SWAP WS
事前に'cvt 'テーブルの1番地に書いておいた値をRCVT命令で読みだしている。
Pops | location: CVT entry number (uint32) |
Pushes | value: CVT value (F26Dot6) |
CVTに事前に値を設定しておく場合はFunitで書くが、RCVTで読みだした値は現在のPPEMに応じたピクセル数単位に変換されて読みだされる。
上のコードでは、読みだした値をWSでStorage Areaの1番地に保存している。
関数化: 関数0
これを関数化した。
回転中心の座標の取得を行い、回転中心のx座標をStorage Areaの0番地に、y座標を1番地に書きこむ関数が関数0である。引数としてグリフの最大の制御点番号(n-1)をとる。参考までにスタックの状態とコメントを書いた*2。
PUSHB_1 0 FDEF /* ..| n-1 | *//* ← initial stack */ PUSHB_1 /* ..| n-1 | 2 | */ 2 ADD /* ..| n+1 | *//* n+1: phantom point n+1 に対応 */ SVTCA[x-axis] /* 座標の測定をx軸方向にする */ GC[orig] /* ..| x_(n+1) | *//* phantom point n+1の(X)座標取得 */ PUSHB_1 /* ..| x_(n+1) | 2.0 | *//* Uint 128 == F24Dot6 2.0 */ 128 DIV /* ..| x_(n+1) / 2 | */ PUSHB_1 /* ..| x_(n+1) / 2 | 0 | */ 0 SWAP /* ..| 0 | x_(n+1) / 2 | */ WS /* ..| *//* StorageArea[0] = x_(n+1) / 2 */ PUSHB_1 /* ..| 1 | */ 1 RCVT /* ..| CVT[1] | *//* read control value table */ PUSHB_1 /* ..| CVT[1] | 1 | */ /* CVT[1]は回転中心y_cに対応 */ 1 SWAP /* ..| 1 | CVT[1] | */ WS /* ..| *//* StorageArea[1] = CVT[1] */ ENDF
グリフの回転
次に回転を計算する部分を考える。
回転行列の計算は地道に計算していくしかないのだが、定数について、小数点以下に12ビット使うようにすることで精度を上げようとした(つまり、6ビット分下駄をはかせている)*3。
定数のセット
'prep'において、Storage Areaの2番地に, 3番地にの近似値を書きこんでいる。程度の近似である。
参考:
その昔、高校の先輩が8ビットマシンで背景が回転するゲームを作るにあたり、sinθ=1/8、cosθ=127/128として、Z80の機械語でいとも簡単に回転行列の計算をしてしまっていたのを見て、この人には追い着けないなあ、と感じたものです。どうやったらこんなの思い付けるんだろう。
— KenKenMkIISR (@KenKenMkIISR) 2016年9月10日
別にこんなことしなくても、定数をPUSHWしてWSすればいいって話でもある。
PUSHB_2 /* |2|1.0| */ 2 64 PUSHW_1 /* |2|1.0|64.0| */ 4096 MUL /* |2|1.0*64.0| */ /* 64(6ビット分)下駄をはかせる */ PUSHW_1 /* |2|1.0*64.0|8.0| */ 512 DIV /* |2|0.125*64.0| */ WS /* | */ /* StorageArea[2] = 0.125*64.0 */ PUSHW_3 /* |3|127.0|64.0| */ 3 8128 4096 MUL /* |3|127.0*64.0| */ /* 64(6ビット分)下駄をはかせる */ PUSHW_1 /* |3|127.0*64.0|128.0| */ 8192 DIV /* |3|(127/128)*64.0| */ WS /* | */ /* StorageArea[3] = (127/128)*64.0 */
'prep'はフォントサイズ(PPEM)が変更される度に呼び出されるプログラムである。
制御点の座標を取得
最初に移動させたい制御点の番号kがスタクトップに乗っているとする。
DUP /* ..|k|k| */ DUP /* ..|k|k|k| */ SVTCA[x-axis] /* X座標に設定 */ GC[cur] /* ..|k|k|x_k| */ SWAP /* ..|k|x_k|k| */ SVTCA[y-axis] /* Y座標に設定 */ GC[cur] /* ..|k|x_k|y_k| */
STVCAで座標軸を変更しながら、GC[0]で制御点kの現在の座標の値を取得している。
平行移動
Storage Areaから回転中心のx, y座標を読みだして、座標から引いている。
PUSHB_1 /* ..|k|x_k|y_k|1| */ 1 RS /* ..|k|x_k|y_k|y_c| */ /* y_c = SA[1]: 回転中心Y座標 */ SUB /* ..|k|x_k|y_k - y_c| */ SWAP /* ..|k|y_k - y_c|x_k| */ PUSHB_1 /* ..|k|y_k - y_c|x_k|0| */ 0 RS /* ..|k|y_k - y_c|x_k|x_c| */ /* x_c = SA[0]: 回転中心X座標 */ SUB /* ..|k|y_k - y_c|x_k - x_c| */
回転
回転を行う関数が関数1である。
/* Function 1: Calculates rotated coordinates with pre-defined angle T */ /* initial stack ..| y | x | x' = x*cosT - y*sinT */ /* final stack ..| y'| x'| while y' = x*sinT + y*cosT */ PUSHB_1 1 FDEF /* ..|y|x| */ DUP /* ..|y|x|x| */ ROLL /* ..|x|x|y| */ DUP /* ..|x|x|y|y| */ ROLL /* ..|x|y|y|x| */ PUSHB_1 /* ..|x|y|y|x|3| */ 3 RS /* ..|x|y|y|x|64*cosT| */ /* SA[3] = 64*cosT */ MUL /* ..|x|y|y|64*x*cosT| */ SWAP /* ..|x|y|64*x*cosT|y| */ PUSHB_1 /* ..|x|y|64*x*cosT|y|2| */ 2 RS /* ..|x|y|64*x*cosT|y|64*sinT| */ /* SA[2] = 64*sinT */ MUL /* ..|x|y|64*x*cosT|64*y*sinT| */ SUB /* ..|x|y|64*(x*cosT - y*sinT)| */ PUSHW_1 /* ..|x|y|64*(x*cosT - y*sinT)|64.0| */ 4096 DIV /* ..|x|y|x*cosT - y*sinT| */ ROLL /* ..|y|x*cosT - y*sinT|x| */ ROLL /* ..|x*cosT - y*sinT|x|y| */ PUSHB_1 /* ..|x*cosT - y*sinT|x|y|3| */ 3 RS /* ..|x*cosT - y*sinT|x|y|64*cosT| */ /* SA[3] = 64*cosT */ MUL /* ..|x*cosT - y*sinT|x|64*y*cosT| */ SWAP /* ..|x*cosT - y*sinT|64*y*cosT|x| */ PUSHB_1 /* ..|x*cosT - y*sinT|64*y*cosT|x|2| */ 2 RS /* ..|x*cosT - y*sinT|64*y*cosT|x|64*sinT| */ /* SA[2] = 64*sinT */ MUL /* ..|x*cosT - y*sinT|64*y*cosT|64*x*sinT| */ ADD /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)| */ PUSHW_1 /* ..|x*cosT - y*sinT|64*(x*sinT + y*cosT)|64.0| */ 4096 DIV /* ..|x*cosT - y*sinT|x*sinT + y*cosT| */ SWAP /* ..|x*sinT + y*cosT|x*cosT - y*sinT| */ ENDF /* ↑関数呼び出し時とx, yの順番を合わせている */
これは、スタックにy座標、x座標が積まれた状態で呼び出す。だいたい7.2°の回転を計算し、計算結果も同様にスタックに積まれる。関数が終了するときのスタック状態が関数開始時と同型になっているので、複数回関数を実行すればそれだけ回転が計算されるというわけである。
回転の繰り返し
回転回数の取得はポイント数取得命令MPSによって得られた整数値をそのまま使っている。頭悪い。
LOOPCALL命令を利用して、(MPSで得られた値)回だけ関数1を実行している。
MPS /* ..|k|y_k-y_c|x_k-x_c|PS| */ /* measure point size. */ /* PS…ポイントサイズ, Uint */ PUSHB_1 /* ..|k|y_k-y_c|x_k-x_c|PS|1| */ 1 LOOPCALL /* ..|k|y_k-y_c|x_k-x_c| */ /* call function 1 PS times */ /* y_k' = y_k-y_c, x_k' = x_k-x_c are changed to y_k'', x_k'' */
平行移動
PUSHB_1 /* ..|k|y_k''|x_k''|0| */ 0 RS /* ..|k|y_k''|x_k''|x_c| */ /* x_c = SA[0]: 回転中心X座標 */ ADD /* ..|k|y_k''|x_k''+x_c| */ SWAP /* ..|k|x_k''+x_c|y_k''| */ PUSHB_1 /* ..|k|x_k''+x_c|y_k''|1| */ 1 RS /* ..|k|x_k''+x_c|y_k''|y_c| */ /* y_c = SA[1]: 回転中心Y座標 */ ADD /* ..|k|x_k''+x_c|y_k''+y_c| */
Storage Areaから回転中心のx, y座標を読みだして、計算した座標に足し算している。
制御点の移動
STVCAで座標軸を切り替えながら、SCFSを利用して、指定した制御点kを計算した座標に移動させている。
SVTCA[y-axis] /* Y座標に設定 */ PUSHB_1 /* ..|k|x_k''+x_c|y_k''+y_c|3| */ 3 CINDEX /* ..|k|x_k''+x_c|y_k''+y_c|k| */ /*stack先頭から3番目をトップにコピー*/ SWAP /* ..|k|x_k''+x_c|k|y_k''+y_c| */ SCFS /* ..|k|x_k''+x_c| */ /* 制御点kのY座標を y_k''+y_c に */ SVTCA[x-axis] /* X座標に設定 */ SCFS /* ..| */ /* 制御点kのX座標を x_k''+x_c に */
CINDEX
ここで、CINDEXは、整数kをpopし、スタックトップからk番目の要素をコピーしてスタックに積む命令である。
Pops | k: stack element number (int32) |
Pushes | ek: kth stack element (StkElt) |
k=1のとき、DUPと同じ動作となる。
関数化: 関数2
これらの、一つの制御点kを移動する命令群を関数2としてまとめた。引数としてグリフ最大の制御点番号n-1, 編集したい制御点番号kを与えて呼び出すと制御点kを回転した位置に移動する。n-1は考えたら使ってないのでもっとシンプルにできるはず。
/* Function 2: moves a control point k to the rotated coordinates */ /* initial stack ..| n-1 | k | n-1: 最大の制御点番号, k: 編集する制御点番号 */ /* final stack ..| */ PUSHB_1 2 FDEF /* ..|n-1|k| */ DUP /* ..|n-1|k|k| */ DUP /* ..|n-1|k|k|k| */ SVTCA[x-axis] /* X座標に設定 */ GC[cur] /* ..|n-1|k|k|x_k| */ SWAP /* ..|n-1|k|x_k|k| */ SVTCA[y-axis] /* Y座標に設定 */ GC[cur] /* ..|n-1|k|x_k|y_k| */ PUSHB_1 /* ..|n-1|k|x_k|y_k|1| */ 1 RS /* ..|n-1|k|x_k|y_k|y_c| */ /* y_c = SA[1]: 回転中心Y座標 */ SUB /* ..|n-1|k|x_k|y_k-y_c| */ SWAP /* ..|n-1|k|y_k-y_c|x_k| */ PUSHB_1 /* ..|n-1|k|y_k-y_c|x_k|0| */ 0 RS /* ..|n-1|k|y_k-y_c|x_k|x_c| */ /* x_c = SA[0]: 回転中心X座標 */ SUB /* ..|n-1|k|y_k-y_c|x_k-x_c| */ MPS /* ..|n-1|k|y_k-y_c|x_k-x_c|PS| */ /* measure point size.*/ PUSHB_1 /* ..|n-1|k|y_k-y_c|x_k-x_c|PS|1| */ 1 LOOPCALL /* ..|n-1|k|y_k-y_c|x_k-x_c| */ /* call function 1 PS times */ /* y_k' = y_k-y_c, x_k' = x_k-x_c are changed to y_k'', x_k'' */ PUSHB_1 /* ..|n-1|k|y_k''|x_k''|0| */ 0 RS /* ..|n-1|k|y_k''|x_k''|x_c| */ /* x_c = SA[0]: 回転中心X座標 */ ADD /* ..|n-1|k|y_k''|x_k''+x_c| */ SWAP /* ..|n-1|k|x_k''+x_c|y_k''| */ PUSHB_1 /* ..|n-1|k|x_k''+x_c|y_k''|1| */ 1 RS /* ..|n-1|k|x_k''+x_c|y_k''|y_c| */ /* y_c = SA[1]: 回転中心Y座標 */ ADD /* ..|n-1|k|x_k''+x_c|y_k''+y_c| */ SVTCA[y-axis] /* Y座標に設定 */ PUSHB_1 /* ..|n-1|k|x_k''+x_c|y_k''+y_c|3| */ 3 CINDEX /* ..|n-1|k|x_k''+x_c|y_k''+y_c|k| * stack先頭から3番目をトップにコピー*/ SWAP /* ..|n-1|k|x_k''+x_c|k|y_k''+y_c| */ SCFS /* ..|n-1|k|x_k''+x_c| *//* 制御点kのY座標を y_k''+y_c に */ SVTCA[x-axis] /* X座標に設定 */ SCFS /* ..|n-1| *//* 制御点kのX座標を x_k''+x_c に */ POP /* ..| */ /*そういえばこのn-1は関数内で全く使ってない*/ ENDF
全ての制御点に対し適用
アイデアは前回の記事で使ったのと同じ。関数に渡す値を用意する部分がやや複雑になっている。
最初にスタックにn-1が積まれてることとする。
/* i = n-1 とおく */ DUP /* ..|n-1|i| */ /* jump_to_here */ DUP /* ..|n-1|i|i| */ PUSHB_1 /* ..|n-1|i|i|0| */ 0 GTEQ /* ..|n-1|i|i<0| */ IF /* ..|n-1|i| *//* if i<0 then */ PUSHB_1 /* ..|n-1|i|2| */ 2 CINDEX /* ..|n-1|i|n-1| */ PUSHB_1 /* ..|n-1|i|n-1|2| */ 2 CINDEX /* ..|n-1|i|n-1|i| */ PUSHB_1 /* ..|n-1|i|n-1|i|2| */ 2 CALL /* ..|n-1|i|n-1|i| *//* call function 2 */ PUSHB_1 /* ..|n-1|i|1| */ 1 SUB /* ..|n-1|i-1| */ PUSHB_1 /* ..|n-1|i-1|20| */ 20 NEG /* ..|n-1|i-1|-20| */ JMPR /* ..|n-1|i-1| *//* goto jump_to_here with i=i-1*/ EIF /* end if */
疑似コードで書くとこんな感じ。
int i = n-1; jump_to_here: if (i>=0){ func2(n-1, i); i--; goto jump_to_here; }
関数化: 関数3
最終的にグリフから実行する処理を関数3としてまとめた。回転中心座標の取得と、全ての制御点に対し回転を実行することをやっている。引数としてグリフの最大の制御点番号n-1を与えて実行する。
PUSHB_1 3 FDEF /* ..|n-1| */ DUP /* ..|n-1|n-1| */ PUSHB_1 /* ..|n-1|n-1|0| */ 0 CALL /* ..|n-1| */ /* call function 0 */ DUP /* ..|n-1|i| */ /* i = n-1 とおく */ /* jump_to_here */ DUP /* ..|n-1|i|i| */ PUSHB_1 /* ..|n-1|i|i|0| */ 0 GTEQ /* ..|n-1|i|i<0| */ IF /* ..|n-1|i| */ /* if i<0 then */ PUSHB_1 /* ..|n-1|i|2| */ 2 CINDEX /* ..|n-1|i|n-1| */ PUSHB_1 /* ..|n-1|i|n-1|2| */ 2 CINDEX /* ..|n-1|i|n-1|i| */ PUSHB_1 /* ..|n-1|i|n-1|i|2| */ 2 CALL /* ..|n-1|i|n-1|i| */ /* call function 2 */ PUSHB_1 /* ..|n-1|i|1| */ 1 SUB /* ..|n-1|i-1| */ PUSHB_1 /* ..|n-1|i-1|20| */ 20 NEG /* ..|n-1|i-1|-20| */ JMPR /* ..|n-1|i-1| */ /* goto jump_to_here */ EIF /* end if */ POP /* ..|n-1| */ POP /* ..| */ ENDF
よく考えてみれば関数2でn-1の値を使ってないので、ループの時点でn-1の値を保存しなくてよくて、もっとシンプルにできたはず。
各グリフのプログラム
PUSHB_2 11 3 CALL
11の部分にはそのグリフの最大の制御点番号が入る。
関数3を、グリフの最大の制御点番号を引数にして呼び出している。
感想
計算量がバカにならないので、割と最新のパソコンでもフォントビューワで表示するとフォントサイズごとに上からポンポンポンと順番に出てくるのがわかる。回転部分をもっと賢くさせたい。
PPEM変わるたびに呼び出されるのが'prep'なので、そこでPPEMを測定しそれに応じた回転行列のパラメータを計算しておくのがいいのだろうと思う。まあそれは追い追い。