[備忘録]アプリ上でpdfMakeにて日本語PDF生成(kotlin編)



・目的
日本語PDFをandroidで出力しよーとすると、haruしかまともなものがないっぽい。
でも、haruでndkつかってビルドとかメンドイので、JSで手軽に出力する。
swiftとkotlinは似てるのし、pdf処理をjsで書いておけば共通化できるので、
楽できるかという目論見。。。


・前準備
[備忘録]アプリ上でpdfMakeにて日本語PDF生成(swift編)と同じ。
ようは使う日本語TrueTypeFontのJS化

・実装
※androidstudio使う。kotlin編なので、kotlinは導入済みとする。


1.
androidstudioでemptyactivityのプロジェクトを作成。
バージョンは、古い方のnexus7しかもってないので、api21のロリポップとした。


2.
プロジェクトをkotlinにする。MainActivity（デフォルトでつくったからそのまま）も、kotlin化

3.
初期状態ではassetsフォルダを作る
場所は、
/プロジェクトフォルダ/app/src/main/
こんな感じ。
![android_1.png](https://qiita-image-store.s3.amazonaws.com/0/38889/a86a9b44-f665-f7d9-03fe-c97a87a695ab.png "android_1.png")


4.
PDFを生成するJSが乗ってるhtmlを作成

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
  </head>
  <body>

    <button id="btn_gen_pdf" type="button">日本語PDF出力</button><br/><br/>

    <script src="js/jquery.min.js"></script>
    <script src="js/pdfmake.js"></script>
    <script src="js/vfs_fonts.js"></script>

    <script>

      var docDefinition;
      // 初期化処理
      $(function() {
        // PDF出力定義
        pdfMake.fonts = {
          tekito_font: {
          normal: 'ipaexm.ttf'
          }
        };
        docDefinition = {
          pageSize: 'A4',
          content: [
            {text: 'This is an sample PDF printed with pdfMake. 日本語のテスト',
               fontSize: 10,
              absolutePosition : { x :15,y :15 }
          }],
          defaultStyle: {font: 'tekito_font'}
        };

        $("#btn_gen_pdf").on("click", function(){
          var _hoge = pdfMake.createPdf(docDefinition);
          _hoge.getDataUrl(function(result) {

            // PDF生成(Base64エンコードされている)
            var encPdf = result.replace(/^data:application\/pdf;base64,/, '');

            // "NativeObj"として、WebViewに紐付けられている場合
            if (NativeObj) {
              // エンコードされた文字列をアプリ上でデコードし、ファイルを出力
              var genPdfPath =  NativeObj.generatePdfOnNative(encPdf);
              console.log("generated:"+genPdfPath);
            } else {
            // とりあえず。。。
              console.log("========");
              console.log("local log:\n" + encPdf);
            }
          });
        });
      });

    </script>
  </body>
</html>
```

iosとの違いは、iosの場合、JSでPDF生成後、フォームをPostして
そのPostをWebViewのデリゲートでキャプチャして処理するが、
AndroidのWebViewの場合は、WebViewのインスタンスにJava(Kotlin)のオブジェクトを
差し込み、JSでPDF生成後（エンコードされた文字列）、JavaオブジェクトをJSの
グローバルオブジェクト？扱いで参照して、そのメソッドで処理する。
また、iosのwebviewは直接PDFを開く事が出来るが、androidのwebviewは
直接開けないないので、android側のImageViewで描画する事にした。


5.
画面レイアウトに、WebView、ImageView追加


```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="gkgkdrink.com.jspdftest.MainActivity">
<WebView
    android:id="@+id/wb_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</WebView>
<ImageView
    android:id="@+id/pdf_preview"
    android:visibility="invisible"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</RelativeLayout>

```

ImageViewは、直接WebViewに生成したPDFを表示できないので、追加した。。。
なんとかならんのかな。。。。

6.
MainActivityのソース

```java
package gkgkdrink.com.jspdftest

import java.io.File
import java.io.FileOutputStream
import java.io.BufferedOutputStream

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.webkit.WebView
import android.webkit.JavascriptInterface
import android.widget.ImageView
import android.util.Base64

import android.os.ParcelFileDescriptor
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.pdf.PdfRenderer

import android.os.Looper
import android.os.Handler

class MainActivity : AppCompatActivity() {

    var wb: WebView? = null
    var storeDir: String? = null
    var pdfBitMap: Bitmap? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        this.wb = this.findViewById(R.id.wb_main) as WebView
        
        storeDir = this.applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).absolutePath

    }


    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        // Javascriptを実行可能にする
        this.wb!!.getSettings().javaScriptEnabled = true
        // 自身を”NativeObj”としてJavascriptから参照かのうなように設定
        // ただし、参照可能なメソッドは、@JavascriptInterfaceでアノテーションする必要がある。
        this.wb!!.addJavascriptInterface(this, "NativeObj")
        this.wb!!.loadUrl("file:///android_asset/main.html")

    }

    @JavascriptInterface
    fun testHoge(): String {

        return "test hoge!!!"
    }

    @JavascriptInterface
    fun generatePdfOnNative(encPdf: String): String {
        Log.d("native", encPdf);
        // Base64エンコードで送られてくるのでデコードする
        var decPdf = Base64.decode(encPdf,Base64.DEFAULT)

        if (!File(this.storeDir!!).exists()) {
            File(this.storeDir!!).mkdir()
        }

        // とりあえず、テキトーにファイル出力
        val pdfPath = this.storeDir!! + "/ttt.pdf"
        val fp = File(pdfPath)
        var fos = FileOutputStream(fp)
        var bos = BufferedOutputStream(fos)
        // 出力ストリームへの書き込み（ファイルへの書き込み）
        bos.write(decPdf)
        bos.flush()
        bos.close()
        Log.d("@@@@","write pdf : " + fp.absolutePath)

        // PDF描画
        renderPdf(pdfPath)
        return pdfPath
    }

    fun renderPdf(pdfPath: String ) {

        // PDFはWebView上で直接表示できないので、
        // ImageViewに切り替えて描画する
        var fd: ParcelFileDescriptor = ParcelFileDescriptor.open(File(pdfPath), ParcelFileDescriptor.MODE_READ_ONLY)
        var renderer: PdfRenderer = PdfRenderer(fd)
        var page: PdfRenderer.Page = renderer.openPage(0)

        var view: ImageView = findViewById(R.id.pdf_preview) as ImageView
        var viewWidth: Int = view.getWidth()
        var viewHeight: Int = view.getHeight()
        var pdfWidth: Float = page.getWidth().toFloat()
        var pdfHeight: Float = page.getHeight().toFloat()

        // 縦横比合うように計算
        var wRatio = viewWidth / pdfWidth;
        var hRatio = viewHeight / pdfHeight;
        if (wRatio <= hRatio) {
            viewHeight = Math.ceil(pdfHeight.toDouble() * wRatio).toInt()
        } else {
            viewWidth = Math.ceil(pdfWidth.toDouble() * hRatio).toInt()
        }
/
        // Bitmap生成して描画
        var bitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
        page.render(bitmap, Rect(0, 0, viewWidth, viewHeight), null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
        pdfBitMap = bitmap

        // WebViewのJavascriptからのメソッド実行は
        // UIスレッドではないらしい。
        // なので、UIスレッド上で実行
        var uiHandler = Handler(Looper.getMainLooper())
        var run = Runnable {
            view.setImageBitmap(this.pdfBitMap!!);
            this.wb!!.visibility = android.view.View.INVISIBLE
            view.visibility = android.view.View.VISIBLE
            Log.d("@@@@@","draw")
        }
        uiHandler.post(run)

    }

}

```

7.
実行してみる

![android_2.png](https://qiita-image-store.s3.amazonaws.com/0/38889/a3c3b688-dd08-d427-ec10-9694113fd99e.png "android_2.png")

ボタンを押す。。。

![android_3.png](https://qiita-image-store.s3.amazonaws.com/0/38889/439e4feb-5087-fd00-bf60-066bf8458b16.png "android_3.png")

出力された。


・TODO、その他。
1.
Androidから実行してるかiOSから実行してるか
JSで切り分けられるよーにする。

2.
PDFの表示方法がWebViewでなんとかできないかどーか調べる。

3.
容量による問題や、生成時の処理中画面ガードはiOSと同じTODO