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に変換して出力する機能を作ってみようと思います!