2010年7月10日土曜日

virtualenv が上手く動作しない場合は -p オプションと --distribute オプションを試す

Python Hackathon という変態の巣窟に来ています。 virtualenv と buildout のハンズオンを受けているのですが、いくつか詰まった点があったのでメモ。

■virtualenv
普通にインストールするとき(ベースとなるpythonのsite-packageを受け継がないようにする場合)は以下のようにします。
python virtualenv.py --no-site-package myenv
ですがこの方法ではMac OS X 10.6付属のPython 2.6.1ではエラーになってしまいました。
New python executable in foo/bin/python
ERROR: The executable foo/bin/python is not functioning
ERROR: It thinks sys.prefix is '/System/Library/Frameworks/Python.framework/Versions/2.6' (should be '/private/tmp/virtualenv-1.4.3/foo')
ERROR: virtualenv is not compatible with this system or executable
そこで https://bitbucket.org/ianb/virtualenv/issue/17/virtualenv-not-working-with-default-python-on-mac-os を参考にして回避策を探してみたところ、
python virtualenv.py --no-site-package -p /usr/bin/python myenv
のようにして-pオプションを使いベースとなるPythonのパスを明示的に指定することで上手い具合にインストールできました。


■buildout
buildoutのbootstrap.pyをダウンロードしてきて実行すればよいのですが、Mac OS X 10.6付属のPython 2.6.1のsetuptoolsはバージョンが低い(0.6c9)ため、以下のようなエラーが出てしまうことがあります。
akisute server$ python bootstrap.py 
The required version of setuptools (>=0.6c11) is not available, and
can't be installed while this script is running. Please install
a more recent version first, using 'easy_install -U setuptools'.

(Currently using setuptools 0.6c9 (/System/Library/Frameworks/Python.framework/Versions/2.6/Extras/lib/python))
もちろんsetuptoolsのバージョンを上げてもいいのですが、あまりデフォルトの環境を汚したくありません。そこで--distributeオプションを使用します。
python bootstrap.py --distribute
こうすると、distributeというsetuptools上位互換のモジュールが自動的にbuildout環境下にインストールされ、その結果上手い具合にインストールに成功します。buildout環境下へのインストールなのでデフォルトの環境は汚れません。すばらしい。

2010年7月7日水曜日

Core Graphics (Quartz) のみで日本語文字列を描画するライブラリのヘッダファイルを書いてみた

とある理由で UIKit の描画機能が使えず、 Core Graphics のみで文字列の描画処理を行わなければならないことになってしまったので、適当に調べてみました。


■ことのはじめ

Core Graphics の機能だけで日本語の文字列を描画する方法については、既に先人の方々が調べて記事にまとめてくださっていたので、そちらを見ていただければ大丈夫です。
http://iphone-dev.g.hatena.ne.jp/ktakayama/20100129
http://d.hatena.ne.jp/r_kurain/20100316
基本的にはこちらで紹介されている方法に従って進めていけば困ることはありません。ただし、描画した文字が上下反対になることがありますので、 CGContextSetTextMatrix を使う箇所を調整したりする必要があるかも。

で、最大の問題になるのがこれらの記事で紹介されている CGFontGetGlyphsForUnichars と呼ばれる関数です。この関数を使えば楽に日本語文字列を文字化けすることなく描画することができるのですが、あろうことかこの関数はプライベートAPIであり、使うとリジェクトされてしまうらしいです。ということで別の作戦をとらなければなりません。

上記の記事にいろいろな対処方法が載っているのですが、私は一番単純に
http://www.mexircus.com/codes/GlyphDrawing.mm
というライブラリを自分のアプリに組み込む方法を試してみることにしました。この方法を使えば、 CGFontGetGlyphsForUnichars でまず作って動くようにしてから、 CMFontGetGlyphsForUnichars と書き換えるだけでそのまま動作するので楽です。

問題はこのライブラリ、mmファイルしか用意されていないのでそのままではうまく使えません。適当にヘッダファイルを用意してやる必要があります。


■と言うわけで書いたもの

と言うわけで GlyphDrawing.h を書いてみました。
http://gist.github.com/466297

ライセンスは元の GlyphDrawing.mm に準じますが、 GlyphDrawing.mm のライセンスが不明なので、不安ならばまず作者さん (http://mexircus.com/blog/) に一言聞いてみるのがいいと思います。


■使い方
  1. http://gist.github.com/466297http://www.mexircus.com/codes/GlyphDrawing.mm をダウンロードします。
  2. GlyphDrawing.mm のインポートを以下のように書き換えます:
    //#import <Foundation/Foundation.h>
    #import "GlyphDrawing.h"
  3. GlyphDrawing.hGlyphDrawing.mm をプロジェクトに追加してビルドします。
  4. CGFontGetGlyphsForUnichars の代わりに CMFontGetGlyphsForUnichars を呼びだせばOKです。


■実際に使ったコードの例
NSString *message = @"本日は快晴なり";

// フォントを設定
// ついでにmessageのサイズも取得
// ただしsizeWithFontは UIGraphics の機能なので、本当に Core Graphics だけで描画したいならここで使ってはいけません
UIFont *font = [UIFont fontWithName:@"HiraKakuProN-W6" size:32.0];
CGRect messageRect = [message sizeWithFont:font];
CGFontRef fontRef = CGFontCreateWithFontName((CFStringRef)font.fontName);
CGContextSetFont(c, fontRef);
CGContextSetFontSize(c, font.pointSize);

// Glyphを作成
size_t length = [message length];
CGGlyph glyphs[length];
UniChar chars[length];
[message getCharacters:chars range:NSMakeRange(0, length)];
CMFontGetGlyphsForUnichars(fontRef, chars, glyphs, length);

// 文字列が上下反対になるのを防止する
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, -1.0);
CGContextSetTextMatrix(c, transform);
CGContextTranslateCTM(c, 0, messageRect.size.height/2);

// 描画
CGFloat x = 100.0f;
CGFloat y = 100.0f;
CGContextShowGlyphsAtPoint(c, x, y, glyphs, length);

2010年6月26日土曜日

別スレッドで NSURLConnection を使うときのメモ

http://twitter.com/griffin_stewie/status/17022582070

@griffin_stewieさんにご指摘いただいたので調べてみました。

別スレッドでNSURLConnectionを使おうとすると、そのままではNSRunLoopのモードの問題なのか、上手い具合にデータを受信することが出来ません。私はメインスレッドでNSURLConnectionを動かすようにして難を逃れたのですが、それ以外にも教えていただいた方法を用いて、以下のように別スレッドのNSRunLoopのモードを変更して対応すると良いみたいです。
    NSURLRequest *theRequest = [NSURLRequest requestWithURL:iTunesURL];
// create the connection with the request and start loading the data
rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed
// to the context for streaming parsing. The handler structure defined above will be used for all the parsing.
// The second argument, self, will be passed as user data to each of the SAX handlers. The last three arguments
// are left blank to avoid creating a tree in memory.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
runMode:beforeDateを使うわけですね。なるほど。