近況報告と今後

近況報告

この一年間、全く関係ない業種で新入社員(社畜)をしておりました。


mashabow様のスライドで当ブログを取り上げ戴いたようで、お礼申し上げます。

「TTXによるフォントのぞき基礎」の資料【5/4更新:スライド公開】
http://d.hatena.ne.jp/mashabow/20130502/1367500832


TTXに関して一点、私の方からも気になった点を書かせて戴きます。
随分前になりますが、CFF形式のフォントを変換した際に、グリフパスの表現が固定小数点の場合*1、正常に変換されなかったと記憶しております。


現在は修正されているかも知れませんが、TTXを使われる際には注意したほうがよろしいかと思います。

今後

社畜生活が続くため、記事の更新は一旦停止します。
記事の内容や、サンプルなどは自由に使ってくださって結構です。


以上 ( ´∀`)ノシ

ギコフォント2

はじめに

以前アスキーアート用のフォント「ギコフォント」を作りました。

ギコフォントを作ろう! - Webと文字


 12ptのビットマップをそのままアウトラインにしたフォントを作る。12pt/24pt/36pt…の倍数でそのとおりに表示されるはず!

今回はその続きです。

専用ページを作った

ギコフォント

他のAA用フォントもあった

どちらもwebfontに対応しており、特にKuma_Liteは記号のみではありますが、140kbとすごく小さな容量を実現させています。

ギコフォントも配信することにした

一度目は

  1. @font-faceがサーバーにアクセス
  2. サーバーはリファラからURLをとってHTMLを解析
  3. 特定のclass内のテキストを抜き出し
  4. テキストのみのgikofontを作成
  5. URL:gikofontをデータベースに保存
  6. クライアントへ返す

二度目以降は

  1. @font-faceがサーバーにアクセス
  2. データベースから読み出し
  3. クライアントへ返す

page=activeだと、毎回HTMLを解析して、文章(md5)に変更があった場合のみフォントを再作成します。

次回

  • 全てのポイント数でスケーリングが可能になったわけではない。よってhdmxテーブルを規定して、特定のピクセルでの文字幅を定義できるか検討する。

OpenTypeレイアウトは厳しかった

前回の投稿から間が開いてすいません。想像以上にOpenTypeレイアウトに手間取っております。以下その報告ですが間違っている可能性が十分にあります

フォントだけじゃ無理?

 私はグリフ置き換えがフォント内のOpenTypeレイアウトテーブル内で完結すると思っていたのですが、そうではないようです。


上の画像はArabic Typesetting Sample*1を使用して、ワードパットでアラビア文字による「アラビア語(اللغة العربية)」をキャプチャしたものです。実はこの文字列の各文字は最初からこの形ではありません。

上の画像は同じフォントを用いてOpenOffice.drawで作ったものです*2

  • 1行目は文字ごとにバラバラです。文字コード→フォントcmap→アウトラインとすると、この孤立型の形で出てきます。
  • 2行目は単語の語尾に当たる部分()が変化しています。アラビア語は右から左に書くので語尾形です。
  • 3行目は単語の頭に当たる部分()が変化しています。
  • 4行目は単語の真ん中に当たる部分()が変化しています。
  • 2〜4を結合して5行目のアラビア文字表現ができます。

この2〜4行目がフォントのGSUB(グリフ置き換えテーブル)を使って置き換わるはずですが、置き換える場所(どこが語尾で語頭なのか)の情報がフォントには存在しません*3


結合による字形入れ替え方法

 色々調べた結果、Unicodeの仕様書にアラビア文字の結合方法が書かれていることがわかりました。参考資料1に解説PDFが有りましたのでキャプチャを示します。

Unicodeの文字データベース…だと!?

Unicodeの文字データベース(Unicode Character Database : UCD)

 Unicodeコンソーシアムは文字一つ一つに対して細かくパラメーターを設定しており、データベースを参考資料2で配布しています。最新バージョンは6.0.0?です。このデータベースを手っ取り早く見たい場合はUCDViewer*4が便利です。

アラビア文字の結合プロパティはjoining propertiesのjt属性に当たります。

[python]UCDから結合プロパティを取り出す

 UCDからプロパティを取り出すプログラムを書きます*5

  1. 参考資料3からucd.all.flat.zipをダウンロード
  2. 解凍。7MB→130MB
  3. repertoireだけsqlite3に入れる*6。(http://codepad.org/8qUfWyXS
  4. ucd.dbができあがり。600MBだけど…
  5. インデックス付ける(http://codepad.org/vkMsYmtI
  6. 取り出す関数を書く(http://codepad.org/XMw9QOSw


[python]グリフベースのテキストクラス(読み飛ばしてください)

 グリフ処理のためにグリフベースのテキストクラスを作りました(http://codepad.org/9AVUUpBL)。
テキストの頭と尻に番兵ノードをおいた双方向*7連結リストです。各グリフにはscriptやGIDといったプロパティの他にfinishedといった処理済みフラグが存在します。GSUB.feature.lookupのレベルでは一度処理されたグリフはスキップされますが、GSUB.featureのレベルでは処理済みのグリフに対しても続けて処理がかけられます。コード93にあるfinishedフラグの初期化は本来ccmp,isol,fina,medi,initの各substituteの後にも必要です。コード95でfinishedフラグを全てOnにしてアラビア文字に対する処理を終了させます。これによって67のイテレーターはその後、アラビア文字を返しません。
 getIterで特定の条件のみを抽出するイテレーターを返します。これはpythonではジェネレーターで表現できます。getGeneは条件を確定したジェネレーターを返すメソッドです。これはクロージャーで表現できます。条件には*,配列,テキストが使用できます。

[python]結合による字形入れ替え方法の実装

 「結合による字形入れ替え方法」を実装します

for e in self.getIter(script='arab',finished=False):
    cclass = UCD.joining(e.unichar);
    e.feature = 'isol'
    if cclass =='R' and \
       (UCD.joining(e.prev.unichar) in ['D','L','C']):
        e.feature = 'fina';
    if cclass =='D':
        if (UCD.joining(e.prev.unichar) in ['D','L','C']):
            if (UCD.joining(e.next.unichar) in ['D','R','C']):
                e.feature = 'medi';
            else:
                e.feature = 'fina';
        else:
            if (UCD.joining(e.next.unichar) in ['D','R','C']):
                e.feature = 'init';

GSUBを適用する

 結合判定が終わったらGSUBを適用します。アラビア文字に関して必要な処理を以下に示します(参考資料4)。

必須の処理はisol(孤立),init(語頭),medi(中間)fina(語尾),rlig(必須合字)です。aから順番に処理していきます。

置き換わったグリフ

結合判定→GSUBとすることで、正しいGIDを取得することができます。

次回

  • アラビア語の送り幅に関して
  • オンライン上に動くサンプルを用意

*1:Microsoft VOLT(http://www.microsoft.com/typography/VOLT.mspx)に同梱されているアラビア語フォント

*2:孤立形以外の文字をどうやって出したかですが、互換性のためにUnicodeは割り当てられていますのでそれを使用しています。

*3:前回解説しなかったChainingContextを使うとできるように思いますが、フォントにはSingleテーブルしか入っていませんでした。

*4:http://vanillasky-room.cocolog-nifty.com/blog/2011/05/ucdviewer.html この人はすごい(;^ω^)

*5:pythonにはunicodedata(http://docs.python.org/library/unicodedata.html)というジャストなパッケージが存在していました…が、目的のプロパティは用意されていませんでした

*6:sql_insert=u"""をsql_insert=u""";にするだけで自動コミットモードになり、生成時間が50秒→一日以上になります。半日ハマった(´・ω・`)

*7:前回解説しませんでしたが、逆から置き換えていくReverseChainingContextものがあるため、双方向にしました

OpenTypeレイアウト

あけましておめでとうございます

         謹┃賀┃新┃年┃
   ∩       2012年 元日
   ∩∪
   ∪.| |∩  .   〃ノヽヽ
.   | |.| |∪     从^∀^) <あけましておめでとうございます
.   | |.| |.| |     ハ∨/^ヽ 
  (∩∩∩∩)   ノ:[三ノ :.'、 
  (∪∪∪∪)  i)、_;|*く;  ノ
.    |=RSK=|  .   |!: ::."T~
  /≠≠≠\.    ハ、___|

1. コンピューターと文字 おさらい

フォントの機能は文字の形をシステムに提供することです。

文字をGIDに直すのは利便性のためです。

2. 合字、字体切り替え、スワッシュ、etc.





フォントは合字を用意したり、アラビア文字の複雑な置き換えを用意したり、グリフ装飾を用意したりできます。

縦書き、ベースライン調整、平均字面、印刷標準字体も用意できます。

これらの機能はOpenTypeレイアウトと呼ばれます。

3. 構成

OpenTypeレイアウトは5つのデータ構造で実現されます。

タグ 意味
BASE ベースライン、平均字面
GDEF
GPOS 位置調整
GSUB 置き換え
JSTF 行揃え

4. データ構造

データ構造は木構造を持つかも知れません。

例を上げて説明します。

4.1. Coverageフォーマット

 データ構造ではCoverageフォーマットが出現します。IndexとGIDの構造体の配列に展開できます。

フォーマット1とその例
フォーマット2とその例

4.2. ClassDefフォーマット

 データ構造ではClassDefフォーマットが出現します。GIDの集合に展開できます。

フォーマット1とその例
フォーマット2とその例

5. GSUB:置き換え

 GSUBは文字列の置き換えを定義します。正確にはGIDの置き換えを定義します。

5.1. ルックアップタイプ1 Single

 1対1の置き換えを定義します。2つのフォーマットが存在します。
フォーマット1はCoverageのGIDにDeltaGlyphIDを足すだけです。

フォーマット2はSubstitute[CoverageのIndex値]が置き換え後のGIDです。

5.2. ルックアップタイプ2 Multiple

 1対多の置き換えを定義します。1つのフォーマットが存在します。

5.3. ルックアップタイプ3 Alternate

 1対多の置き換えを定義します。1つのフォーマットが存在します。

5.4. ルックアップタイプ4 Ligature

 多対1の置き換えを定義します。1つのフォーマットが存在します。
最初の文字に対して、その後ろに続く文字列と置き換えを複数定義することができます。
例)f→[([f,l],ffl),([l],fl),([fi],ffi),([i],fi)]

追記予定


ギコフォントを作ろう!

モナーフォントとは


上の画像のように文字で書かれた絵をアスキーアートといいます。アスキーアートMS Pゴシックの12ptと決まっています。これがMS明朝だと以下のようになってしまいます。

つまり線が大きくズレてしまうわけですね。このため、MS Pゴシックが存在しない環境でアスキーアートを表示するモナーフォントが開発されました。これはMS Pゴシックとごく似たような文字を含ませたフリーのフォントです*1。下の図はモナーフォントで表示したアスキーアートです。一枚目とそれほど変わりがないと思います。

モナーフォントの問題点

修正: 2011/12/22
 モナーフォントにも問題があります。

  • AAを表示するためだけに用意されたフォントなのに二種類のグリフを含むことは容量のムダ。最近はやりのWebFontとして配信する場合も大変手間が掛かります。
  • 1ドットのズレも許せない私のような原理主義者にとって、モナーフォントは耐えられない。

 仮にMSPゴシックをそのまま違う環境に持ってきたとしても、以下の問題があります。

  • Macのような埋め込みビットマップを使用しない環境では、WindowsのMSPゴシックを入れても上手く表示されません。
  • AAを表示するためだけに用意されたフォントなのに二種類のグリフを含むことは容量のムダ。
  • ライセンス

ギコフォントをつくろう!

修正: 2011/12/22
 12ptのビットマップをそのままアウトラインにしたフォントを作る。12pt/24pt/36pt…の倍数でそのとおりに表示されるはず!

著作権・ライセンスの問題

こまけぇこたぁry)
書体の著作権は認められていないのでよしと判断…。ただ、不正競争防止法とかライセンス契約とかは…<(^o^)>

MSGOTHIC.TTC→ 002.TTF

 WindowsフォントフォルダにあるMSGOTHIC.TTCを参考資料1のUniteTTCで分割します。

埋め込みビットマップ抜き出し

 TTFファイルから埋め込みビットマップを抜き出します。埋め込みビットマップの仕様は参考資料2を参照してください。ここでは自作のpythonフォントライブラリttfを使用して抜き出します。またFontForge用の送り幅設定用テキストも同時に作成しています。

from ttf import ttf
from PIL import Image

#002.TTF is MS P Gothic File Name.
f = open('002.TTF',"rb");
ms_p_gothic = ttf(f);

#FontForge script
script = open('font_forge_script.txt','w');

cmap = ms_p_gothic.readTable('cmap');
#pratformID = Microsoft(3),encodingID = UCS2(1)
ms_encoding=cmap.getSubTable(3,1);
GIDMap = ms_encoding.charcode2gindex;

for Unicode,GID in GIDMap.items():
    eblc = ms_p_gothic.readTable('EBLC');
    #12pt/em -(96dpi,1pixel=1dot)-> 16pixel/em
    ebdt_offset = eblc.getOffset(GID,16,16);
    #Is there GID in EBDT ?
    if ebdt_offset is False:
        continue;
    
    ebdt = ms_p_gothic.readTable('EBDT');
    #ebdt_offset = point's count array
    eb_bitmap = ebdt.getGlyph(ebdt_offset[0]);

    h_ascender = eb_bitmap['bitmapSizeTable']['hori']['ascender'];
    h_bearing_x= eb_bitmap['metrics']['horiBearingX'];
    h_bearing_y= eb_bitmap['metrics']['horiBearingY'];    
    h_anvance_width = eb_bitmap['metrics']['horiAdvance'];
    script.write("Select(" + str(Unicode) + ')\n');
    script.write("SetWidth(" + str(h_anvance_width*10) + ')\n');

    glyph = eb_bitmap['glyph'];
    width = eb_bitmap['metrics']['width'];
    height = eb_bitmap['metrics']['height'];
    #(255,255,255) is white.
    img = Image.new("RGB",(16,16),(255,255,255));
    for y in range(height):
        for x in range(width):
            if glyph[y*width+x] ==1:
                #(0,0,0) is black.
                try:
                    img.putpixel((x+h_bearing_x,y+(h_ascender-h_bearing_y)),(0,0,0));
                except:
                    print GID,Unicode;
                    
    large_img=img.resize((16*10,16*10));
    large_img.save("glyphs/u" + str('%X' % Unicode) +".bmp");

script.close();
f.close();

 このスクリプトを実行すると、uXXX.bmpという画像がglyphsフォルダに作成されます。XXXにunicode16進数が入ります。


 注意として、元の画像をそのまま1000%拡大したものを保存します。こうしないと、次で説明するpotraceでうまくトレースされません。また「」の一文字だけ、BearingXが負になるために、正しく画像化されません。この点はFontForgeで手動で修正します。

トレース

 自動トレースソフト「potrace」(参考資料3)でビットマップ画像をSVGベクターデータに変換します。以下の一文を書いたバッチファイルを実行することで、glyphsフォルダに同名のSVGファイルが作成されます。

potrace glyphs/*.bmp -s -a 0

FontForge

 フォント作成ソフト「FontForge」を用意します。今回はWindows用のバイナリを使用しました(参考資料4)。起動してNewを選んだ後、メニューバーから以下の操作を実行します。

  • エンコーディングエンコーディング変換→ISO 10646-1(Unicode,BMP)を選択
  • エレメント→フォント情報→名前→フォント名→「giko」にする
  • エレメント→フォント情報→名前→著作権情報→任意に修正
  • エレメント→フォント情報→Grid Fitting→65535,NoGridFit,No-Anti-Aliasに設定
  • エレメント→フォント情報→一般情報→高さを140、深さ「20」にして、EMSize「160」を確認。(FontForgeは画像の上端をアセンダラインに、下端をディセンダラインにあわせて取り込みます。)
  • エレメント→フォント情報→OS/2→その他、Charsets→http://www28095u.sakura.ne.jp/webmozi/fdv/002.TTF/OS2を見て設定する。

以上が終わったら、メニューバーからファイル→取り込み→フォーマットをSVGテンプレート、背景で使用のチェックをオフにして、glyphsフォルダ内の任意のuXXX.svgを選択します。

微調整

 取り込んだグリフは全てがベースラインから1unit下にずれていました。メニューバーからファイル→スクリプトを実行を選択して、以下を入力してOKを押します。

SelectAll()
Move(0,1)

次に送り幅を設定します。スクリプトを実行から呼び出すボタンを押して、font_forge_script.txtを指定します。OKボタンを押すとグリフに対して送り幅が設定されます。

「も」の修正


x < 0でカットされてしまった部分を手動で付け足します。画像の赤くなっている場所がそうです。

調整

  1. Ctrl+Aでグリフを全選択して、メニューバーからヒント→ヒントを削除 を選択します。
  2. Ctrl+Aでグリフを全選択して、エレメント→単純化→単純化 を選択します。
  3. Ctrl+Aでグリフを全選択して、エレメント→座標を丸める→整数に を選択します。

otf出力

 メニューバーからファイル→フォント出力を選択し、OpenType(CFF)、ビットマップフォント無し、Validate Before Savingのチェックボックスをオフにして、保存ボタンを押します。upemが2の乗数になっていないと文句行ってきますが、無視して保存します。

sfd出力

 メニューバーからファイル→保存でプロジェクトが保存できます。

確認

 出力されたgiko.otfを確認します。

WebFontでテストします
http://www28095u.sakura.ne.jp/gikofont/testpage.html

ファイル(2011/12/20更新)

FontForgeスクリプト
sfd: http://www28095u.sakura.ne.jp/gikofont/file/giko.sfd
・フォント
otf: http://www28095u.sakura.ne.jp/gikofont/file/giko.otf
ttf: http://www28095u.sakura.ne.jp/gikofont/file/giko.ttf
・WebFont(IE)用フォント
eot: http://www28095u.sakura.ne.jp/gikofont/file/giko.eot
MAC/Linuxインストール用フォント (名前をMSPGothicにした。そのままインスールすればAAが見れる)


otf: http://www28095u.sakura.ne.jp/gikofont/file/MS-PGothic.otf
ttf: http://www28095u.sakura.ne.jp/gikofont/file/MS-PGothic.ttf

次回

フォントの修正 [追記2012/3/30]

修正1

ビットマップが入っていない文字(空白など)が含まれていないため、ズレズレでした。
スクリプトを以下のように修正し、文字幅テキストを作成します。

#out bitmap character script
advance_width = open('width.txt','w');
#…
    if ebdt_offset is False:
        advance_width.write("Select(" + str(Unicode) + ')\n');
        advance_width.write("SetWidth(" + str(hmtx.getMetric(GID)[0]*160/256) + ')\n');
#…

これをFontForgeに読み込ませ、幅のみのグリフを作成します。

修正2

upemを160から1024に変更しました。

修正3

gaspを修正しました

TTF/OTF Reader

 自作フォント解析ツール。もともとGAE(GoogleAppEngine)で作っていたけど、さくらVPS512+ngin0.8+apache2.2+mod_wsgi3.3+python2.7.1+django1.3に移植。まだフォントのアップロードができないですが、フォントフォーマットの勉強に良かったら使ってみてください(´・ω・`) safari,Chrome,FireFox,IE9以上で見てください。IE8,Operaは一部の機能が使えません。

旧:http://fontconverter.appspot.com/
新:http://www28095u.sakura.ne.jp/webmozi/fdv/

異体字…IVS、フォント、GSUB

 世の中には姓名にちょっとでも違う字を使うとうるさく言う奴がいて、そんなこんなでUnicodeに字形切り替えの仕組みが備わった。これをIVSという。特に「ナベ」さんはうるさかったらしく、字形も沢山登録された。

上の図はUnicodeに登録されている異体字リスト(IVD:http://unicode.org/ivd/data/2007-12-14/IVD_Charts.pdf*1)から引っ張ってきた。実は上に挙げたのは一部である。日本人しか使わない、日本人のための表である。ああ素晴らしい( ´∀`)。


 こういう規格が何で出てきたのかとか、以下のサイトが詳しかった。
  IVSとフォントの関係 - ちくちく日記
  IVSとGSUBはどう違うのか - Mac OS Xの文字コード問題に関するメモ

ともかく、私も日曜フォントプログラマーとして、フォント内部のIVS構造がどうなっているのかを話したい。

仕様

 フォントの仕様の部分はここ(Character/Glyph Index Mapping)のFormat 14: Unicode Variation Sequencesに当たる。バイナリのデコンパイル実装は自分で頑張ってください。私が作ったのはこれ*2Chromeブラウザで閲覧することをおすすめする。で、デコンパイルした結果が下。フォントのサンプルにはHanaMinOTPr6N-Regular.otfを使用させていただいた。


http://fontconverter.appspot.com/cmap?platformID=0&encodingID=5&fontname=HanaMinOTPr6N-Regular.otf


見て分かる通り、VS(varSelector)に対して、レコードが幾つといったように、通常とは逆のデータ構造に成っている。defaultUVSOffsetは気にしなくてもいい。nonDefaultUVSOffsetがUnicodeとグリフIDを結びつけているレコードになる。defaultUVSOffsetとnonDefaultUVSOffsetの数値をクリックすると表示を展開するので見て欲しい。

対応表

 上のデコンパイル結果を見ても、どのVSがどの表示に対応しているか分からないと思うので、並び替えてリストを作った。ソートされていないのはご愛嬌。


http://fontconverter.appspot.com/cmap2?platformID=0&encodingID=5&fontname=HanaMinOTPr6N-Regular.otf


Unicode#VSとなっている。対応しているグリフIDは右に表示される。クリックしたらグリフに飛ぶ。リストの一番下に37001の定義がある。キャプチャした画像が以下。


http://fontconverter.appspot.com/glyph?fontname=HanaMinOTPr6N-Regular.otf&GID=7947

http://fontconverter.appspot.com/glyph?fontname=HanaMinOTPr6N-Regular.otf&GID=7948


点が有ったり無かったり。極めてどうでもいい、些細な違いのあるグリフがフォント内には別々のIDを振られて格納されている。

GSUB

GSUBとは字形置き換えの仕組みである。IVSの仕組みと何が違うのといわれれば、IVSはUnicodeの仕様、GSUBはOpenTypeFontの仕様である。だからGSUBは字形を置き換える用途ならなんでもありになる。縦書き用のグリフを置き換えたり、アラビア文字の置き換えを定義したり、大体なんでもできる。GSUBの中味を見てみよう。仕様のテーブルの解説はいつかまた。


http://fontconverter.appspot.com/GSUB2?fontname=HanaMinOTPr6N-Regular.otf


featureというのが用途だと思えば良い。14のjp78のmaptableのリンクを見て欲しい。


http://fontconverter.appspot.com/GSUB3?subtableindex=0&lookupindex=4&fontname=HanaMinOTPr6N-Regular.otf


original GlyphIDが置き換え前のグリフID、substitute GlyphIDが置き換え後のグリフIDである。試しに、index:3の行を見て欲しい。以下キャプチャ。


http://fontconverter.appspot.com/glyph?fontname=HanaMinOTPr6N-Regular.otf&GID=23
http://fontconverter.appspot.com/glyph?fontname=HanaMinOTPr6N-Regular.otf&GID=6210


なんだかゴチャゴチャっとしたグリフに置き換わっているのがお分かりいただけただろうか。こんな感じで言語や用途を指定した上で、使用するグリフをすげ替えてしまうのがGSUBテーブルである。もちろん、今見せたような1対1の置き換えだけではなくて1対多、逆に多対1、特定のグリフ列担った場合に置き換えるなど色々できる。

まとめ

 日曜フォントプログラマーとして、IVSとGSUBをフォントフォーマットの観点から語ることが出来た。残念ながら「ナベ」さんだけが異状にうるさい理由は分からなかったが、まあよしとしよう。

お詫び

 公開しているプログラム(http://fontconverter.appspot.com/)はバグフィックス中です。エラーコードの表示が出ても許してください☆(ゝω・)vキャピっ