2012年9月21日金曜日

AndroidでPDFを作成してみる

実現方法としては・・・

  1. 既存ライブラリを活用する
  2. 自前でスクリプトを書く
  3. PDFを生成してくれるWebサービスを利用する
  4. アプリからインテントを使い生成する

僕自身、全て試したわけではありませんが、手っ取り早いのは(1)既存のライブラリを活用する事だと思います。実装が一筋縄ではありませんが・・・


(2)に関しては、PDFの仕様を理解していないとイバラの道になると思います。

でも仕様を理解することは勉強にもなるし、時間に余裕のある人はこちらをオススメします。(日本語訳の公式リファレンスが5、6千円で売ってます。英語のリファレンスはWebで無料でダウンロードできます)

(3)に関しては2chで仕入れた情報ですが、面倒臭そうなので調べていません…

(4)調べたんですが、今のところ無さそうです。。。(もしあれば教えてください)


既存のライブラリを利用してみる

これより先はライブラリの利用方法について説明しますが、フリーのライブラリを利用する際、

いくつか注意する事があります。

  1. ライブラリが利用しているjavaパッケージ
  2. ライブラリが利用しているJDKのバージョン(JDK6を使ってたらアウト)
  3. 日本語等の出力
  4. ライセンス

これらすべての条件を満たすライブラリとなると、かなり数が限られてきそうです。

特にライセンスは重要です。調べれば分かることですが、GPL関連のライセンスだと利用するのが難しいでしょう。

うろ覚えですが、「GPLライセンスライブラリを利用する際の条約の一つとして、アプリに組み込まれたライブラリが改変された場合はソースコードを開示しなければいけない。Androidではこの条約を満たすことが出来ないから、結局ソースコードを開示する必要がある」どこかのブログに書いてあった気がします。。。回避策としては、PDFに関連する処理だけを抜き出し、別アプリで公開後、インテント経由で利用するということが考えられます。でもユーザーにアプリインストール促す必要もあり、いろいろ面倒ですね。。。


なんとなく使えそうなライブラリ

java PDF ライブラリ」でググルと結構ヒットしますが、どれもコレも一筋縄ではいきません。

java用のPDFライブラリ
ライブラリ packageのサポート JDKバージョン 日本語出力 ライセンス
iText × ×
iText for Android ×
PDF Box ×
Android PDF Write ×


iText

iTextはドキュメントも豊富で、ブログ等で使い方も紹介されているぐらいメジャー?なライブラリのようですが、

awt等のAndroidではサポートされないパッケージを使ってます。

実際にiTextを実装する際、この事に気づくまで1週間ぐらいハマってしまいました。

しかもGPLの派生ライセンスを採用しています。


iText for Android

iTextAndroidで使えるようにしてるらしいですが、どうやらまだ未完成のようです。

実際試しましたが、結局ビルドが通りませんでした。


PDF Box

こちらは試してませんが、やっぱりパッケージ関連の問題で無理なようです。

http://www.kaede-software.com/2011/04/pdfbox.html


Android PDF Write

恐らくつい最近に公開されたAndroid用のライブラリですが、外国の方が作られたライブラリなので日本語等の出力が無理なようです。英語のドキュメントのみ生成するのであれば、十分利用出来ます。


色々なライブラリを調べた結果、「androidで利用出来るjavaPDFライブラリ」は今のところ無さそうです・・・


NDKを利用する

それからかなり悩んだのですが、javaが無理ならCのライブラリはどうだ?と色々探してみたところ、

libHaru free PDF library」を見つけました。

ドキュメント等は全て英語のなのですが、製作者は日本の方なので、日本語の出力も可能です。

ライセンスは zlib/libpngライセンスなので、商用・非商用を問わずアプリに組み込めそうです。

ダウンロード・ドキュメントはこちら

http://libharu.org/wiki/Main_Page


これよりNDKの環境構築とビルドに関して説明していきます。環境を整えるのが結構面倒ですが、それに見合った価値はあると思います。

(とりあえず動きを確認したい場合は、ページ最下部にプロジェクトをダウンロードできるURLを貼りつけています)


NDKの開発環境を整える

環境構築に関しては、分かりやすく解説されているサイトが多数存在しますので、頑張って調べてください。

僕はこちらのサイトを参考にさせていただきました。

http://www.usefullcode.net/2010/12/android_sdk_inst05.html

http://d.hatena.ne.jp/bs-android/20090324/1237864333


ネィティブモジュールを呼び出す準備

クラスファイルにネィティブを使うことを宣言します。

static {      System.loadLibrary("CreatePDF");  }  private native int createPDF(String fpath);  


exportボタンを押すと、SyncTaskが起動してPDFを作成するようにします。

@Override  public void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);      setContentView(R.layout.main);            Button b = (Button)this.findViewById(R.id.Button01);      b.setOnClickListener(new OnClickListener() {                    @Override          public void onClick(View v) {                            switch (v.getId()) {              case R.id.Button01:                                    export();                  break;              default:                  break;              }          }      });  }  

プログレスダイアログを表示して、ネイティブに処理を任せる。

private void export() {            final ProgressDialog mProgress =          new ProgressDialog(this);      mProgress.setProgressStyle(ProgressDialog.STYLE_SPINNER);      mProgress.setMessage(getResources().getString(R.string.wait));      mProgress.setCancelable(false);            Runnable successRun = new Runnable() {          public void run() {                            Toast.makeText(getApplicationContext(),                      getResources().getString(R.string.success),                      Toast.LENGTH_SHORT).show();              mProgress.dismiss();              setResult(RESULT_OK);          };      };      Runnable errorRun = new Runnable() {          public void run() {                            Toast.makeText(getApplicationContext(),                      getResources().getString(R.string.error),                      Toast.LENGTH_SHORT).show();              mProgress.dismiss();          };      };                  mProgress.show();      ExportSyncTask task = new ExportSyncTask(              this.getApplicationContext(),              successRun, errorRun);      task.execute();  }    private String getSavePath(String catStr) throws IOException {            String pathSd = new StringBuilder()          .append(Environment.getExternalStorageDirectory().getPath())          .append("/")          .append(this.getPackageName())          .toString();      File filePathToSaved = new File(pathSd);            if (!filePathToSaved.exists() && !filePathToSaved.mkdirs()) {          throw new IOException("FAILED_TO_CREATE_PATH_ON_SD");      }            return pathSd + "/" + catStr;  }    public int exportPDF () throws FileNotFoundException, IOException {            String savePath;      savePath = getSavePath("helloPDF.pdf");      createPDF(savePath);        return 0;  }    private class ExportSyncTask extends AsyncTask<Void, Void, Integer> {        private Handler mHandler;      private Handler mHandler2;      private Thread mSuccessT;      private Thread mErrorT;            public ExportSyncTask(Context context, final Runnable r1, final Runnable r2) {                    mHandler = new Handler();          mHandler2 = new Handler();                    mSuccessT = new Thread(new Runnable() {              public void run() {                  mHandler.post(r1);              }          });                    mErrorT = new Thread(new Runnable() {              public void run() {                  mHandler2.post(r2);              }          });      }            @Override      protected Integer doInBackground(Void... arg0) {                    try {              exportPDF();          } catch (FileNotFoundException e) {              Log.e("PDF_sampleActivity", e.toString());              return -1;          } catch (IOException e) {              Log.e("PDF_sampleActivity", e.toString());              return -1;          }                    return 0;      }            @Override      protected void onPostExecute(Integer result) {                    if (result == null || result < 0) {                            mErrorT.start();          } else {                            mSuccessT.start();          }      }  }  


マニフェストパーミッションを追加しておきます。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  


「CreatePDF」はこれから作成するライブラリの名前になります。

javad側の処理はこれで完了です。

ここで一旦ビルドしてください。

ソースコードダウンロードする

AndroidでlibHaruPDFを使うには、ソースコードコンパイルしてAndroid用の共有ライブラリを作成する必要があります。

libHaruPDFはlibpngライブラリーを使用しておりますので、libHaruPDFとlibpngのソースコードダウンロードしましょう。

libHaruPDF

http://libharu.org/wiki/Downloads

(Latest Versionをダウンロードして下さい)


libpng

http://www.libpng.org/pub/png/libpng.html

(バージョン1.2.46のsource codeよりダウンロードして下さい)


ではeclipseにフォルダを作って、ファイルを移動しましょう。

jniフォルダを作り、その中にlibharu,libpng,pdftestの3フォルダを作ります。

フォルダ構成は次のようになってると思います。(*.c,*.hのみ移動させます。その他のmakeとか必要ないです)

top    src    gen    assets    libs    res    jni    --libharu      --include        --hpdf_annotation.h        --hpdf_catalog.h        --hpdf_conf.h        --...      --hpdf_annotation.c      --hpdf_array.c      --hpdf_binary.c      --...    --libpng      --<*.hと*.cを全て>    --pdftest  

pdftestフォルダにCreatePDFとヘッダファイルを配置します。

こちらのブログを参考にヘッダファイルを作成します。(「JNIヘッダファイルの生成」の項を参照)

http://d.hatena.ne.jp/bs-android/20090324/1237864333

次にCreatePDF.cを作成してください。

下記URLのサンプルコードを試してみます。(コピペでは動きません)

http://libharu.org/wiki/Documentation/Examples

/*   * << Haru Free PDF Library 2.0.0 >> -- font_demo.c   *   * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>   *   * Permission to use, copy, modify, distribute and sell this software   * and its documentation for any purpose is hereby granted without fee,   * provided that the above copyright notice appear in all copies and   * that both that copyright notice and this permission notice appear   * in supporting documentation.   * It is provided "as is" without express or implied warranty.   *   */    #include <sample_pdf_PDFsampleActivity.h>  #include <stdlib.h>  #include <stdio.h>  #include <string.h>  #include <setjmp.h>  #include "hpdf.h"  #include <android/log.h>    jmp_buf env;    static char* jstr2cp(JNIEnv *e, jstring str);  static void jstrfree(JNIEnv *e, jstring str, char *cp);  static void log_print(char *str);    #ifdef HPDF_DLL  void  __stdcall  #else  void  

0 件のコメント:

コメントを投稿