読者です 読者をやめる 読者になる 読者になる

Quartz で 日本語 font が使いたい on iPhone

CGFontGetGlyphsForUnichars に代わる関数を呼び出せる実装を用意したよ。という話です。

最近 iPhone アプリ開発にはまっているのですが、iPhone の 高速な 2D 描画機能である Quartz で日本語を使うのに苦労したので書き残しておきます。

Quartz は Mac 向けの OSX でも採用されている、PDF の操作なども出来る高速な 描画フレームワークです。Objectice-C から呼び出せますが、基本的には C の関数を呼び出して使うようです。

例によって Apple のドキュメントが素晴らしくまとまっており、Webから閲覧出来ます。

http://developer.apple.com/mac/library/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html

この資料にも、Text という章があって次のようなコードで文字の描画できるとあります。

void MyDrawText (CGContextRef myContext, CGRect contextRect) // 1
{
    float w, h;
    w = contextRect.size.width;
    h = contextRect.size.height;

    CGAffineTransform myTextTransform; // 2
    CGContextSelectFont (myContext, // 3
                    "Helvetica-Bold",
                     h/10,
                     kCGEncodingMacRoman);

    CGContextSetCharacterSpacing (myContext, 10); // 4
    CGContextSetTextDrawingMode (myContext, kCGTextFillStroke); // 5
    CGContextSetRGBFillColor (myContext, 0, 1, 0, .5); // 6
    CGContextSetRGBStrokeColor (myContext, 0, 0, 1, 1); // 7
    myTextTransform =  CGAffineTransformMakeRotation  (MyRadians (45)); // 8
    CGContextSetTextMatrix (myContext, myTextTransform); // 9
    CGContextShowTextAtPoint (myContext, 40, 0, "Quartz 2D", 9); // 10
}

上記資料から引用しています。

文字列がMacRomanでコーディングされていればこれで良いのですが、日本語の場合はうまくいきません。フォント指定の

    CGContextSelectFont (myContext, // 3
                    "Helvetica-Bold",
                     h/10,
                     kCGEncodingMacRoman);

がそもそも使えないと資料には有ります。で、どうするかというと、SelectFontに変えて次の関数を使います。

    CGFontRef font = CGFontCreateWithFontName((CFStringRef)@"HiraKakuProN-W3");
    CGContextSetFont(myContext, font);
    CGContextSetFontSize(myContext, fontSize);

さらに、文字列描画の関数は、CGContextShowTextAtPoint が使えなくなるので、

void CGContextShowGlyphsAtPoint (
   CGContextRef c,
   CGFloat x,
   CGFloat y,
   const CGGlyph glyphs[],
   size_t count
);

を利用します。が、ここで問題になるのが、CGGlyph です。 CGGlyph は文字の内部表現で、文字ごとに値の決まっている unsigned short の数値です。文字ごとに決まっている値なのですが、フォント種類ごとに違う値が指定されているのでどうにか、文字コードで定義されている、文字の値と、フォントで指定されるこの CGGlyph の値をマッピングしなければなりません。

そのための関数が

void CGFontGetGlyphsForUnichars(CGFontRef, const UniChar[], const CGGlyph[], size_t);

で、 unicode の格納された文字列を渡すと簡単に CGGlyph が手に入ります。が、しかし、この関数は ドキュメントが無く、iPhone アプリで使うとAppleからリジェクトされることが判明している関数なので問題になります。

というのがまとめられているのがこちらのエントリ

http://iphone-dev.g.hatena.ne.jp/ktakayama/20100129

で、どうするか。

上のエントリーを見るとわかるのですが、いくつかの独自実装があるのでそちらを使います。高速で thread safe な事になっている、
http://www.mexircus.com/codes/GlyphDrawing.mm
の実装はうまくimport 出来なかったので、2dゲームフレームワークの cocos2d のコードから必要部分だけを抜き出したファイルを作りました。
http://gist.github.com/raw/333978/d96a1a7a9f9824b8e3c8cf3ac6c45bf383b73763/GlyphTable.m
からダウンロード出来ます。

使用例はこんな感じになります。

    NSString* str = @"描画したい文字列";
    CGFontRef font = CGFontCreateWithFontName((CFStringRef)@"HiraKakuProN-W3");
    fontTable* tbl = readFontTableFromCGFont(font);
    int originalLen = [str length];
    CGGlyph _glyphs[[str length]];
    unichar _chars[[str length]];
    int i;
    for(i = 0; i < [str length]; i++) {
        _chars[i] = [str characterAtIndex:i];
    }

    size_t griphLen = 0;
    mapCharactersToGlyphsInFont(tbl, _chars, originalLen, _glyphs, &griphLen);

ライセンスはもとのファイルと同じく Apache Licence 2.0 になりますので安心して使えます。