にせねこメモ

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

アウトラインがぶれるフォント

TrueType命令で遊ぶシリーズ。

  1. 初めてのTrueType命令: Windowsでは見えないフォントをつくる - にせねこメモ
  2. フォントサイズに合わせて回転するフォントを作る(1) - にせねこメモ
  3. PPEM・ポイントサイズを表示するフォント - にせねこメモ
  4. TrueType命令で三角関数(sin, cos)を計算する - にせねこメモ
  5. フォントサイズに合わせて回転するフォントを作る(2) - にせねこメモ
  6. TrueType命令で擬似乱数: 線形合同法 - にせねこメモ
  7. TrueType命令でビット演算 - にせねこメモ

擬似乱数を使って何か

前に擬似乱数を生成する関数を作ったので、それを使って何かやってみようというのが今回の目的である。
アウトラインをぶれさせてみたら、ランダムさが効果的に使えるのではないかと思うので、それをやってみる。

完成品

f:id:nixeneko:20170606181753p:plain
M+フォント(mplus-1p-regular.ttf)のアウトラインをガタガタにしている。フォントサイズによって文字の形が変わっているところに注目してほしい。

イラレとかで大きさを変えていくのを見ても面白い。


ダウンロード

なお、例によってTrueType命令を利用しているのでMacでは動かない。

実装

やっていることは単純で、各制御点の座標を読み出し、それに対して乱数生成関数によって生成した乱数を(適当にスケールして)足し合わせ、その座標に制御点を動かす。これをX, Y軸、および全ての制御点に対して行う。

さて、実際に実装してみる。

初期化

各種値の初期化は'prep'で行う。

まず、擬似乱数関数に与える初期seedを適当に初期化する。できるだけ乱雑になってほしかったのでMPPEM命令でPPEMを取得してそれを初期seedとした。これにより、フォントサイズによって乱数列が異なり、最終的に得られるアウトラインもフォントサイズ依存になる。
このseedをStorage Area 0番地に保存する。

次に、乱数のスケールに使用するscale factor sを準備する。
XまたはY軸方向に移動する距離の最大値(つまり、スケール後の乱数の絶対値の最大)をCVTテーブルの0番に書いておく。ここでは30 (FUnits)とした。
これをRCVTで読みだすとpixel単位になるので、それを pとする。
乱数の最大値は0x7FFFFFFFであるので、 s = \mathrm{0x7FFFFFFF} / pとする。
このとき、 p < 1の場合に、 sを計算する際にオーバーフローしてしまうため、それを防ぐために p := 1.0 \mbox{ (if } p < 1 \mbox{)}という処理を入れている。

読みだした乱数を sで割ることで、乱数(の絶対値)が0~pの範囲に含まれる様になる。
最後にscale factor  sをStorage Areaの1番地に保存している。

#seed初期化
PUSHB_1
 0
MPPEM   # 初期seed (>0)
WS      # StorageArea[0] = PPEM

#スケール係数の初期化
PUSHW_7 #0xFF|256.0|0xFF|256.0|0xFF|256.0|0x7F|
 255   # 0xFF
 16384 # 256.0
 255   # 0xFF
 16384 # 256.0
 255   # 0xFF
 16384 # 256.0
 127   # 0x7F
MUL     #0xFF|256.0|0xFF|256.0|0xFF|0x7F00|
ADD     #0xFF|256.0|0xFF|256.0|0x7FFF|
MUL     #0xFF|256.0|0xFF|0x7FFF00|
ADD     #0xFF|256.0|0x7FFFFF|
MUL     #0xFF|0x7FFFFF00|
ADD     #0x7FFFFFFF| #2147483647 = 0x7FFFFFFF
PUSHB_1
 0
RCVT    #0x7FFFFFFF|CVT[0]|
DUP     #0x7FFFFFFF|CVT[0]|CVT[0]|
PUSHB_1 #0x7FFFFFFF|CVT[0]|CVT[0]|1.0|
 64
LT      #0x7FFFFFFF|CVT[0]|CVT[0]<1.0|
IF      #0x7FFFFFFF|CVT[0]|  #if CVT[0]<1.0:
 POP     #0x7FFFFFFF|
 PUSHB_1 #0x7FFFFFFF|1.0|
  64
EIF
DIV     #0x7FFFFFFF/CVT[0]|
PUSHB_1 #0x7FFFFFFF/CVT[0]|1|
 1
SWAP    #1|0x7FFFFFFF/CVT[0]|
WS    #StorageArea[1] = 0x7FFFFFFF/CVT[0]

Storage Areaへの割り付け

番号 value
0 seed (PPEMで初期化)
1 scale factor

関数群

関数0: 乱数の生成

前に作った乱数関数(改良版の関数1)をもってきて、関数0とする。
関数番号以外は同一なので詳しくは前の記事を参照。

実行する度に1~0x7FFFFFFFの乱数列が返ってくる。

関数1: スケールした乱数の取得

関数0を呼び出して乱数を取得し、'prep'でStorage Areaの1番地に保存したscale factor  sで割ることでスケールする。
この際、取得した乱数を1ビット左シフトし、取得した乱数の上から2ビット目を符号ビットとして扱うことで、負の数も得られる様にしている。
最終的に得られる乱数は、最大動き幅pについて-p~pの範囲となるはず。

/* Function 1: returns scaled random value */
PUSHB_1
 1
FDEF          /* ..|      *//* ← initial stack */
PUSHB_1
 0
CALL          /* ..|rand| */
DUP
ADD           /* ..|rand<<1| */ /* to make it signed */
PUSHB_1
 1
RS            /* ..|rand<<1|s| */ /*s = StorageArea[1], scaling factor */
DIV           /* ..|(rand<<1)/s| */
ENDF

関数2: 指定された制御点をランダムな大きさだけ動かす

指定された制御点番号kに対応する制御点のX座標、Y座標を取得し、それに対して関数1で取得したスケール済み乱数を足しあわせ、計算された座標にSCFSで移動している。

/* Function 2: moves the control point k         */
/* initial stack ..| k |       k: 編集する制御点番号                           */
/* final stack   ..|                                                      */
PUSHB_1
 2
FDEF     /* ..|k| */
DUP      /* ..|k|k| */
DUP      /* ..|k|k|k| */
DUP      /* ..|k|k|k|k| */
SVTCA[x-axis]                                  /* X座標に設定 */
GC[cur]  /* ..|k|k|k|x_k| */
PUSHB_1
 1
CALL     /* ..|k|k|k|x_k|rand| */ /* get scaled random value*/
ADD      /* ..|k|k|k|x_k+rand| */
SCFS     /* ..|k|k| */        /* 制御点kのX座標を x_k + rand に */

SVTCA[y-axis]                                  /* Y座標に設定 */
GC[cur]  /* ..|k|y_k| */
PUSHB_1
 1
CALL     /* ..|k|y_k|rand| */
ADD      /* ..|k|y_k+rand| */
SCFS     /* ..| */            /* 制御点kのY座標を y_k + rand に */

ENDF

関数3: カウントアップしつつ関数2を呼び出す

LOOPCALLで呼び出される用の関数。スタックトップの番号(カウンター)を引数として関数2を呼び出し、最後にカウンターを1増やす。

/* function 3: call func2 with value n and increment n */
/* initial stack: ..|n|   */
/* final stack:   ..|n+1| */
PUSHB_1
 3
FDEF      /* ..|n| */ /* repeat n times */
DUP       /* ..|n|n| */
PUSHB_1
 2
CALL      /* ..|n| */ /* moves the point n */
PUSHB_1   /* ..|n|1| */
 1
ADD       /* ..|n+1| */
ENDF

関数4: LOOPCALLで関数3を指定回数実行する

グリフから呼び出され、指定回数(=制御点の個数回)関数3を実行する。関数3では呼び出す度にスタックトップの値(カウンター)を1ずつ増やしていくので、すべての制御点に対して操作が行われることになる。

/* function 4: call func3 n times */
/* initial stack: ..|n|  n: number of repetition */
/* final stack:   ..|  */
PUSHB_1
 4
FDEF      /* ..|n| */ /* n: num of points in the glyph */
PUSHB_1   /* ..|n|0| */ /* initialize the counter by 0 */
 0
SWAP      /* ..|0|n| */
PUSHB_1   /* ..|0|n|3| */
 3
LOOPCALL  /* ..| */ /* call function 3, n times */
ENDF

グリフ

関数4を、グリフから次のように呼び出す。
例えば、グリフに含まれる制御点の個数が10個であれば、

PUSHB_2
 10
 4
CALL

となる。

すべてのグリフへの適用

'cvt ', 'fpgm', 'prep'を設定して書き出したフォントAmovepointsrandom.ttfをttxを使ってXMLファイル(.ttx)にダンプする。

ttx Amovepointsrandom.ttf

その後、次のPythonプログラムを実行し、Amovepointsrandom.ttxのすべてのグリフに対して関数を呼び出すTrueType命令を適用した結果(Amovepointsrandom-out.ttx)を得る。
やっていることは、グリフ毎に制御点の個数を調べ、その値を組み込んだ関数呼び出しの命令を付加するという感じ。

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

import xml.etree.ElementTree as ET

INFILE = "Amovepointsrandom.ttx"
OUTFILE = "Amovepointsrandom-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 ="""
          PUSHB[ ]      /* 2 values pushed */
          {} 4
          CALL[ ]       /* CallFunction */
          """.format(cnt)
        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)

最後に得られた.ttxファイルをttxで.ttfに変換する。

ttx Amovepointsrandom-out.ttx

これによって生成されるAmovepointsrandom-out.ttfが完成品である。

コード

'cvt '

number value
0 30

'fpgm'

PUSHB_1
 0
FDEF
PUSHB_1
 0
RS
DUP
PUSHW_2
 30000
 14488
ADD
DUP
ROLL
SWAP
PUSHW_1
 4096
MUL
DIV
ROLL
ROLL
PUSHB_1
 3
CINDEX
PUSHW_1
 4096
MUL
MUL
SUB
PUSHW_3
 4096
 30000
 18271
ADD
MUL
MUL
SWAP
PUSHW_2
 3399
 4096
MUL
MUL
SUB
DUP
PUSHB_1
 0
GT
IF
ELSE
PUSHW_7
 255
 16384
 255
 16384
 255
 16384
 127
MUL
ADD
MUL
ADD
MUL
ADD
ADD
EIF
DUP
PUSHB_1
 0
SWAP
WS
ENDF
PUSHB_1
 1
FDEF
PUSHB_1
 0
CALL
DUP
ADD
PUSHB_1
 1
RS
DIV
ENDF
PUSHB_1
 2
FDEF
DUP
DUP
DUP
SVTCA[x-axis]
GC[cur]
PUSHB_1
 1
CALL
ADD
SCFS
SVTCA[y-axis]
GC[cur]
PUSHB_1
 1
CALL
ADD
SCFS
ENDF
PUSHB_1
 3
FDEF
DUP
PUSHB_1
 2
CALL
PUSHB_1
 1
ADD
ENDF
PUSHB_1
 4
FDEF
PUSHB_1
 0
SWAP
PUSHB_1
 3
LOOPCALL
ENDF

'prep'

PUSHB_1
 0
MPPEM
WS
PUSHW_7
 255
 16384
 255
 16384
 255
 16384
 127
MUL
ADD
MUL
ADD
MUL
ADD
PUSHB_1
 0
RCVT
DUP
PUSHB_1
 64
LT
IF
POP
PUSHB_1
 64
EIF
DIV
PUSHB_1
 1
SWAP
WS