2009年10月31日土曜日

libHaru on iPhone: Objective-Cでエラーハンドリング



前回記載できなかったエラーハンドリングについて軽くさわってみました。


■libHaruのエラーハンドリング
http://libharu.org/wiki/Documentation/Error_handlingに詳しく書いてあります。要するに、一番最初のPDFドキュメントを生成するところでエラーハンドラ関数を渡したらあとはエラーが発生するたびにその関数が呼び出されるみたいです。
// まずはヘッダファイルの中で
// ユーザーデータ用の構造体の宣言と、エラーハンドラ関数のプロトタイプ宣言をしておく

typedef struct _PDFService_userData {
HPDF_Doc pdf;
PDFService *service;
NSString *filePath;
} PDFService_userData;

// エラーハンドラ関数のシグネチャは常にこうでなければならない
// 関数名は自由に決めて良いが、引数と返り値は決められている
void PDFService_errorHandler(HPDF_STATUS error_no,
HPDF_STATUS detail_no,
void *user_data);
// 注意点として、エラーハンドラ関数はHPDF_Newより前の行で実装しておかなければならない
// (プロトタイプ宣言しておいてもだめみたいです)
void PDFService_errorHandler(HPDF_STATUS error_no,
HPDF_STATUS detail_no,
void *user_data) {
// ユーザーデータを取得する
// C言語の構造体からでもObjective-Cのクラスが取り出せますよ
PDFService_userData *userData = (PDFService_userData *)user_data;
HPDF_Doc pdf = userData->pdf;
PDFService *service = userData->service;
NSString *filePath = userData->filePath;

// エラーハンドリング後、PDF関連の処理を続行したい場合は
// このHPDF_ResetError関数を呼び出す必要がある。
// これを呼び出さないと後続の処理がうまくいかない。
HPDF_ResetError(pdf);

// 逆にエラーハンドリング後、PDF関連の処理をすべて中断したい場合には
// HPDF_Free関数を使ってpdfオブジェクトを解放するとよい。こうすると、以後の
// HPDF関連の関数はすべて何もしないで終了してくれる。ただし、同じpdfオブジェクトを
// 2回HPDF_Freeしてしまうとエラーになるため、HPDF_HasDocを使って制御すること
HPDF_Free(pdf);
}

// あとはPDFドキュメント生成時に関数とユーザーデータを引数として渡す
int main(int argc, char** args) {
PDFService_userData userData;
HPDF_Doc pdf = HPDF_New(PDFService_defaultErrorHandler, &userData);
userData.pdf = pdf;
userData.filePath = @"/var/tmp/hogehoge.pdf";
// 以下省略
}


■CとObjective-Cをつなげる
C言語的にはこれでよいのですが、Objective-C、ひいてはCocoa Touchフレームワーク的にはこういうエラー処理はdelegateにしてしまったほうが便利です。たとえば、PDFファイルを生成するためのObjective-CクラスPDFServiceに、PDFServiceDelegateを持たせて運用してみましょう。libHaruのエラーハンドラ関数を以下のように作ってみます。
void PDFService_defaultErrorHandler(HPDF_STATUS   error_no,
HPDF_STATUS detail_no,
void *user_data)
{
// ユーザーデータを展開
PDFService_userData *userData = (PDFService_userData *)user_data;
HPDF_Doc pdf = userData->pdf;
PDFService *service = userData->service;
NSString *filePath = userData->filePath;

// 以後のPDF作成処理をすべてストップする
HPDF_Free(pdf);

// Delegate呼び出し
if (service.delegate) {
[service.delegate service:service
didFailedCreatingPDFFile:filePath
errorNo:error_no
detailNo:detail_no];
}
}
あとはこのDelegateを適当なViewControllerなどに準拠させて、
#pragma mark -
#pragma mark delegate method
- (void)service:(PDFService *)service
didFailedCreatingPDFFile:(NSString *)filePath
errorNo:(HPDF_STATUS)errorNo
detailNo:(HPDF_STATUS)detailNo
{
NSString *message = [NSString stringWithFormat:@"Couldn't create a PDF file at %@\n errorNo:0x%04x detalNo:0x%04x",
filePath,
errorNo,
detailNo];
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"PDF creation error"
message:message
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] autorelease];
[alert show];
}
とすると、冒頭のイメージのようになります。

2009年10月24日土曜日

libHaruを使ってiPhoneアプリからPDFを作成する

ある日突然、iPhoneからPDFファイルを作ったら面白そうだと思ったので、早速試してみることにしました。既に先駆者の方がいらっしゃるかと思ったのですが、調べてもそれらしい資料が無いので自力でなんとかしてみることにしました。需要はあまり無いかと思いますが、何かのお役に立てば幸いです。


■PDFファイルを生成する方法
まずはPDFファイルを生成する方法を知らなければ話になりません。ということで、調査しました。
  • 一からライブラリを自作する
    jspdf(http://code.google.com/p/jspdf/)などという漢気あふれるライブラリがあるのですから、やってやれないことはない!と思い、さっそくPDFの仕様書(http://www.adobe.com/devnet/pdf/)をAdobe社からダウンロードしてきたのですが、31MB, 500ページ以上あります。なにこれこわい。
  • Adobe PDF Library(http://www.adobe.com/devnet/pdf/library/ http://www.est.co.jp/pdfl/index.html
    本家のライブラリです。日本ではイースト社がライセンスしてるみたいですが、どう見ても有償です。チェンジお願いします。
  • libHaru(http://libharu.org/wiki/Main_Page
    こうして散々探した挙句ついに見つけた至高のPDFライブラリがこちら。C言語で書かれており、iPhoneでもバッチリ動きそうです!もちろんオープンソース!素晴らしい!
ということで、今回はlibHaruを使ってみようと思います。


■libHaruをXcodeでビルドする
1.libHaruのソースコードをダウンロードしてXcodeプロジェクトに追加
最新のソースコードをhttp://libharu.org/wiki/Downloadsからダウンロードしてきて解凍します。このまま自分のMacで使えるようにするだけなら. configureしてmakeしてmake installすれば一発なのですが、あいにくそれではiPhoneで動かすことが出来ません。
includeディレクトリにヘッダファイルが、srcディレクトリにCソースコードが入ってますので、これら全部を自分のXcodeプロジェクトにコピーして取りこみます。


2.libpngのソースコードをダウンロードしてXcodeプロジェクトに追加
libHaruを用いてPDFに画像を出力したいときには、別途libpngが必要になります。そこで、libpngも一緒にビルドすることにします。libpngのソースコードはhttp://www.libpng.org/pub/png/libpng.htmlから入手できますので、これをダウンロードしてきてXcodeに追加してビルドすればよいのですが、ここではもっと簡単で確実に動く方法をご紹介します。そう、「既にlibpngをビルドして使っている他のiPhoneプロジェクトからパクる」です!(もちろん、きちんとライセンスに違反していないことを確認しましょう)
おあつらえ向きなことに、cocos2d for iPhoneの0.8.1以降(http://code.google.com/p/cocos2d-iphone/source/browse/#svn/trunk)では内部的にlibpngを使用しています。これを真似しない手はありません。さっそくcocos2d for iPhoneの最新ソースを取得し、Xcodeでプロジェクトを開いて、Support/external/libpng以下のソースをコピーして自分のプロジェクトに追加しましょう。


3.libHaruをconfigureする
. configureで動けばいいのですが、多分. configureしても今の自分のMacの環境に合わせてConfigureされてしまい、iPhoneでは動かないだろうと思ったので、自分の手でconfigureすることにしました。といっても、直すのは一箇所だけです。includeディレクトリに入っていたhpdf_config.h.inファイルを以下のように修正します。
/* include/hpdf_config.h.in.  Generated from configure.in by autoheader.  */

/* Define to 1 if you have the header file. */
#undef HAVE_DLFCN_H
#define HAVE_DLFCN_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_INTTYPES_H
#define HAVE_INTTYPES_H 1

/* Define to 1 if you have the `png' library (-lpng). */
#undef HAVE_LIBPNG
#define HAVE_LIBPNG 1

/* Define to 1 if you have the `z' library (-lz). */
#undef HAVE_LIBZ
#define HAVE_LIBZ 1

/* Define to 1 if you have the header file. */
#undef HAVE_MEMORY_H
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDINT_H
#define HAVE_STDINT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STDLIB_H
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRINGS_H
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_STRING_H
#define HAVE_STRING_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_STAT_H
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_SYS_TYPES_H
#define HAVE_SYS_TYPES_H 1

/* Define to 1 if you have the header file. */
#undef HAVE_UNISTD_H
#define HAVE_UNISTD_H 1

/* debug build */
#undef HPDF_DEBUG
#ifdef DEBUG
#define HPDF_DEBUG 1
#endif

/* debug trace enabled */
#undef HPDF_DEBUG_TRACE
#ifdef DEBUG
#define HPDF_DEBUG_TRACE 1
#endif

/* libpng is not available */
#undef HPDF_NOPNGLIB

/* zlib is not available */
#undef HPDF_NOZLIB

/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

/* Define to the full name of this package. */
#undef PACKAGE_NAME

/* Define to the full name and version of this package. */
#undef PACKAGE_STRING

/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME

/* Define to the version of this package. */
#undef PACKAGE_VERSION

/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
#define STDC_HEADERS 1

/* Define to `unsigned int' if does not define. */
/* #undef size_t */
書き換えたら、ファイル名をhpdf_config.hに変更します。


4.Xcodeから新規ビルドターゲットを作成する
ソースコードの準備が出来たら、次はソースコードをビルドしてlibharu.aとlibpng.aを生成するためのビルドターゲットを新規に作成します。ビルドターゲットは使い方を覚えると非常に便利です。




最初にlibpngのビルドターゲットを作ります。libpngのビルドターゲットは、2.でご紹介したとおりcocos2d for iPhoneの最新ソースのXcodeプロジェクト内に既に用意されていますので、これを真似して作るとよいです。ただし、libpngをビルドする際にzlib.dylibが必要になりますので、自分のプロジェクトにzlib.dylibを追加するようにしておいてください。最初からiPhone SDKの一部として用意されています。



続いてlibHaruのビルドターゲットを作成します。こんな感じです。



最後に、libharu.aを自分のiPhoneプロジェクトの依存ライブラリに追加します。


5.レッツコンバイン!
これでビルドする準備が整いましたので、いよいよビルドします。
緊張の一瞬!Cmd+Bをプログラムドライブ!!



できました!!!!


■Hello, libHaru!
無事にビルドが完了いたしましたので、次はいよいよPDFを生成してファイルに出力してみます。
// TEST CODE: testing libharu
NSString *path = nil;
const char *pathCString = NULL;
NSLog(@"[libharu] PDF Creation START");
HPDF_Doc pdf = HPDF_New(NULL, NULL);
NSLog(@"[libharu] Adding page 1");
HPDF_Page page1 = HPDF_AddPage(pdf);
NSLog(@"[libharu] SetSize page 1");
HPDF_Page_SetSize(page1, HPDF_PAGE_SIZE_A4, HPDF_PAGE_LANDSCAPE);
NSLog(@"[libharu] TextOut page 1");
HPDF_Page_BeginText(page1);
HPDF_UseJPFonts (pdf);
HPDF_UseJPEncodings (pdf);
HPDF_Font fontEn = HPDF_GetFont(pdf, "Helvetica", "StandardEncoding");
HPDF_Font fontJp = HPDF_GetFont(pdf, "MS-Mincyo", "90ms-RKSJ-H");
HPDF_Page_SetFontAndSize(page1, fontEn, 16.0);
HPDF_Page_TextOut(page1, 100.00, 100.00, "Hello libHaru!");
HPDF_Page_SetFontAndSize(page1, fontJp, 16.0);
HPDF_Page_TextOut(page1, 100.00, 60.00, [@"はろー日本語" cStringUsingEncoding:NSShiftJISStringEncoding]);
HPDF_Page_EndText(page1);
NSLog(@"[libharu] Path drawing page 1");
HPDF_Page_SetLineWidth(page1, 4.0);
HPDF_Page_SetRGBStroke(page1, 1.0, 0, 0);
HPDF_Page_Rectangle(page1, 200, 200, 40, 150);
HPDF_Page_Stroke(page1);
NSLog(@"[libharu] PNG image drawing page 1");
path = [[NSBundle mainBundle] pathForResource:@"portrait2"
ofType:@"png"];
pathCString = [path cStringUsingEncoding:NSASCIIStringEncoding];
NSLog(@"[libharu] LoadPngImageFromFile path:%@\n pathCString:%s", path, pathCString);
HPDF_Image image = HPDF_LoadPngImageFromFile(pdf, pathCString);
HPDF_Page_DrawImage(page1, image, 300, 50, 245, 319);

// Get documents directory
NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask,
YES);
path = [arrayPaths objectAtIndex:0];
path = [path stringByAppendingPathComponent:@"test.pdf"];
pathCString = [path cStringUsingEncoding:1];
NSLog(@"[libharu] SaveToFile path:%@\n pathCString:%s", path, pathCString);
HPDF_SaveToFile(pdf, pathCString);
NSLog(@"[libharu] Freeing PDF object ");
HPDF_Free(pdf);
NSLog(@"[libharu] PDF Creation END");

これを実行すると、アプリのDocumentディレクトリにtest.pdfが生成されるはずです。


■実機で試してみる
シミュレータでは動くようになりましたが、実機で動かなければ何の意味もありません。ということで、いよいよ実機でテストしてみます!生成したPDFファイルを表示するためのUIWebViewを作成し、実機で表示してみた結果がこちら。



Hooray!!


■さて次は
いよいよ次からが本番、UIViewの中身をPDFに変換して出力する機能を作ってみようと思います!

2009年10月23日金曜日

iPhone向けに最適化されたPNGをlibpngで扱う方法

メモ。
iPhoneアプリのバンドルに同梱したPNGファイルは、ビルド時に最適化処理が行われてしまうため、そのままではlibpngで読み込むことが出来ません。iPhone向けに最適化されたPNGをlibpngで扱う方法は、いまのところ二つ。


1:最初から最適化をしないようにする
参考にしたページはこちら。
http://d.hatena.ne.jp/wasabi-arts/20090301/1235856525


このように、IPHONE_OPTIMIZE_OPTIONS=-skip-PNGsを追加するか、


またはこのように、最初っから圧縮をしないように設定する。


2:最適化したファイルをいったんUIImageで読み出して、再度PNGでファイル書きだしする
いったんUIImageを使って当該ファイルをロードして、ファイルにpng形式で書き出せばよいらしいです。
(@nakamura001さんありがとうございます!)

[2009/10/24 22:20追記]
@nakamura001さんがご自身のブログで検証結果をアップされてます。
http://d.hatena.ne.jp/nakamura001/20091024/1256371800

UIButtonからPNG画像を抜く方法ですが、これを応用すればUIButtonに最適化されたPNGを使用していても普通のPNG画像を取得することが出来ます。私の環境でもテストしたところ、うまくいきました!