にせねこメモ

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

単語の最初のみ(最後のみ)に置換を行うOpenType Feature Fileの書き方

OpenType Feature FileでフォントのGSUBの設定を行っているのですが、単語の始めだけ指定して置換したい場合がしばしばあります。
今までは少し泥臭い方法(後述)でやっていたのですが、 Adobe の OpenType Feature File Specification ページにそのものずばりの例が上がっていたので、それについて。

なお実際に試したり確認してないコードもあるので、動かなかったらごめんなさい。考え方はあってると思いますので。

単語の最初のみの置換について、スマートなやり方

単語の最初のみにマッチ

Example 2. Matching a beginning-of-word boundary:

ignore substitute @LETTER f' i';
substitute f' i' by f_i.begin;

http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html#5.f.ii

ignore substitute は、指定されたものにマッチした場合、それについて置換を行わないというものです。
@LETTERクラスを単語に含まれる可能性のある全ての文字を含むように定義しておき、 ignore substitute で @LETTER クラスの文字に続く文字の連続について置換を行わないよう指定することで、単語の始めにのみマッチするようにできる、とのこと。

「f i の並びを置換せよ。ただし、@LETTERに含まれる文字に続く f i については除く」ということで、 f i の前に@LETTERのこない最初の文字だけが置換されます。


上の例では特定のグリフの並びだけですが、クラスに対してなどの指定もできます。

  ignore substitute @LETTER @hoge';
  substitute @hoge' by @hoge_begin;

適当に @hoge と @hoge_begin クラスを用意すると、単語の最初にくる @hoge クラスの文字が @hoge_begin に置換されます。
詳しくは OpenType Feature File Specification | Adobe Developer Connection “ignore substitution”の Example 4 など参照ください。


単語の末尾だけにマッチ

そうすると、同様に単語の終わりだけにマッチするものも作れますね。

  ignore substitute f' i' @LETTER;
  substitute f' i' by f_i.end;

ほかの単語の一部でないものだけにマッチ

単語の一部でない文字の並びに対して置換したい(例えば、「and」を別のグリフに置換えたいが「handle」の時には置換えたくないといった場合)、次の様にignoreで両方指定するといいようです。

Example 3. Matching a whole word boundary:

ignore substitute @LETTER a' n' d', a' n' d' @LETTER;
substitute a' n' d' by a_n_d;

http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html#5.f.ii

単語の真ん中だけにマッチ

ignore substitute は使いませんが、一応。単語の真ん中だけ置換を行います。「handle」の中の“and”を置換えるが「and」単体や「hand」の様に端に来ている場合は置換えないようになります。

  substitute @LETTER a' n' d' @LETTER by a_n_d;

泥臭い方法

ignore substitute を使わない方法についても書いておきます。
簡単のため数字について考えることにします。
グリフクラスを以下のように用意しておきます。また、入力される文字は @digits のものだけを考えます。

@digits = [zero one two three four five six seven eight nine];
@digits.alt = [zero.alt one.alt two.alt three.alt four.alt
               five.alt six.alt seven.alt eight.alt nine.alt];
@digits.begin = [zero.begin one.begin two.begin three.begin
                 four.begin five.begin six.begin seven.begin
                 eight.begin nine.begin];
@digits.end = [zero.end one.end two.end three.end four.end
               five.end six.end seven.end eight.end nine.end];
@digits.mid = [zero.mid one.mid two.mid three.mid four.mid
               five.mid six.mid seven.mid eight.mid nine.mid];
#すべての数字
@ANYDIGIT = [@digits @digits.alt
             @digits.begin @digits.end @digits.mid];

図の n は普通の @digit, a は @digits.alt, b は @digits.begin, e は @digits.end, m は @digits.mid を示します。

単語の最初のみ

数字の後に続く数字を置き換えようとする場合、

substitute @ANYDIGIT @digits' by @digits.alt;

とすると、数字の並びが与えられた場合、最初だけ @digits で残り、二文字目以降がすべて @digits.alt のグリフに置き換えられます。
そのため、これを利用して、

#数字をすべて @digits.begin に置き換え
substitute @digits by @digits.begin;
#数字の後に続く @digits.begin を @digits で置き換え
substitute @ANYDIGIT @digits.begin' by @digits;

のような設定をします。上と下は別々の lookup にして、この順番で適用するようにしてください。
すると、最初のみ @digits.begin のグリフになり、2文字目以降は普通の数字 @digits という様になります。
f:id:nixeneko:20150509104320p:plain

単語の最後のみ

最初だけに適用する場合と同様に、

#数字をすべて @digits.end に置き換え
substitute @digits by @digits.end;
# @digits.end の後に数字(先に全て置き換えたので即ち@digits.end)が続く
# 場合、 @digits で置き換え
substitute @digits.end' @digits.end by @digits;

とします。上と下で別々の lookup にして、この順番で適用します。
すると、最後のみ @digits.end のグリフになり、2文字目以降は普通の数字 @digits という様になります。
f:id:nixeneko:20150509104630p:plain

単語の真ん中のみ

これは、最初に適用する場合と最後に適用する場合の組み合わせです。

# 最初以外の数字を @digits.alt に置き換え
substitute @ANYDIGIT @digits' by @digits.alt;
# 次に @digits.alt が続く @digits.alt を @digits.mid に置き換え。
substitute @digits.alt' @digits.alt by @digits.mid;
# 最後の桁に @digits.alt が残っているので @digits に置き換え。
# (@digits.altをそのまま表示してよければやらなくて良い)
substitute @digits.alt by @digits;

とすると、先頭と末尾を除いた真ん中が @digits.mid になります。
f:id:nixeneko:20150509104356p:plain

単語の先頭、真ん中、末尾を分ける

筆記体などで前後に接続する場合、独立形、語頭形、語中形、語尾形などと分けられれば便利です。

# 最初以外の @digits を @digits.end に置き換え
substitute @ANYDIGIT @digits' by @digits.end;
# @digits.end の前の @digits (つまり最初の数字)を @digits.begin に
# 置き換え。
substitute @digits' @digits.end by @digits.begin;
# 次に @digits.end が続く @digits.end を @digits.mid に置き換え。
substitute @digits.end' @digits.end by @digits.mid;

これにより、1文字の数字は @digit 、2文字以上の数については、最初の数字が @digits.begin 、真ん中の数字が @digits.mid 、末尾の数字が @digits.end となります。
f:id:nixeneko:20150509124915p:plain

これをアルファベットなどに対して使えば、筆記体の接続などに使えるかと思います。もっといい方法はあると思いますが。