LoginSignup
2
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-09-23

・目的
haruを使わずに日本語PDFを出力したい。(日本語をテキストとして出力したい)
できれば、android側もkotolinで書いて移植しやすいよーにしたい。

・前準備
※Mac上で実行したが、多分、Windowsでも問題ない(と思いたい)

1.
bower,gruntをインストール
(nodeはインストールされている事前提)

sudo npm install -g grunt-cli
sudo npm install -g bower

bowerいらんかも。。。orz

2.
githubからソースダウンロードし解凍
https://github.com/bpampuch/pdfmake

3.解凍したフォルダにもぐり、ライブラリインストール

npm install grunt-text-replace grunt-browserify grunt-contrib-uglify grunt-dump-dir grunt-contrib-concat
npm install runt-mocha-cov grunt-jsdoc runt-contrib-jshint

4.
フリーのTrueTypeのフォントを落としてくる
とりあえず、ここから。
http://ipafont.ipa.go.jp/
で、解凍。

5.
「2.」で解凍したフォルダの examples/fontsフォルダ下の、Roboto*.ttfファイルをすべて消し、
「4.」で解凍したttfファイルを置く
(明朝を使うので、とりあえず明朝の方)

6.
「2.」で解凍したフォルダ下で、コマンド実行

grunt dump_dir

build下の「vfs_fonts.js」が、解凍したフォント用に置きかわる。

前準備終わり。

・iOS側実装

1.
xcodeでシングルビューのswiftのプロジェクト作成

2.
pdfをJSで生成するhtmlを準備

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <button id="btn_gen_pdf" type="button">日本語PDF出力</button><br/>
    <div style="display:none;">

        <!--
         nativeactionとかいう、テキトーなプロトコルをつけ、
         UIWebviewにてhandleする。
         メソッドはPDFがエンコードされてダラダラ長いのでPostとする。。。
         -->
        <form id="frm_generate_pdf" method="post" action="nativeaction://generated_pdf">
            <input type="hidden" name="enc_pdf" id="enc_pdf" value=""/>

            <!-- 以下パラメータはいらんけど、とりあえず、
             postのパラメータテストの為に付加した。。。 -->
            <input type="hidden" name="fuga"  value="tttt"/>
            <input type="hidden" name="ggg"  value=""/>
        </form>
    </div>
    <script src="./jquery.min.js"></script>
    <script src="./pdfmake.js"></script>
    <script src="./vfs_fonts.js"></script>
    <script>


        var docDefinition;

        // 初期化
        $(function(){

          pdfMake.fonts = {
            tekito_font: {
            normal: 'ipaexm.ttf'
            }
          };
          $("#btn_gen_pdf").on("click",function(){
                               genPdf();
                               return false;});

          docDefinition = {
            content: [
                      {text: 'This is an sample PDF printed with pdfMake. 日本語のテスト',
                       style: 'tekito_style'
            }],
            styles: {
                tekito_style: {
                fontSize: 25
                }
            },
            defaultStyle: {font: 'tekito_font'}
          };
        });

        // PDF生成
        function genPdf() {
            try {

                var _hoge = pdfMake.createPdf(docDefinition);

                // pdfMake.createPdf().open()は
                // mobileSafariでは実行されない
                // なので、Base64でエンコードされた文字列を
                // フォームでpostする(多分、Getはダメ。。。)
                //
                // getDataUrl(function)はopenの中でやってるメソッド。
                // pdfをエンコードして文字列にしてる
                _hoge.getDataUrl(function(result) {

                    // 「data:application/pdf;base64,」は
                    // ios上で正規表現使って置換するのは面倒くさいので、ここで除去。。。
                    $("#enc_pdf").val(result.replace(/^data:application\/pdf;base64,/, ''));
                    $('#frm_generate_pdf').submit();
                });
            } catch(e) {
                alert(e);
            }
        }

        // PDF表示
        function handleCreatePdfFromiOS(pdfPath) {
            // めんどくさいので、webviewからjavascriptキック
            location.href = pdfPath;
        }
    </script>

  </body>
</html>



プロジェクトにてきとーにフォルダを掘り、コピー
(jsファイルもコピー(「vfs_fonts.js」は、前準備で作ったフォントファイル))
ios_1.png

3.
ストリーボード上のVCのビューにWebView貼り付けて、
constraint全画面にし、vcのソースに紐付け

4.
VCのソースをこんな感じで。。。

import UIKit

// UIWebViewDelegeteでリクエストをキャプチャする
class ViewController: UIViewController,UIWebViewDelegate {

    @IBOutlet weak var wb: UIWebView!

    // リクエストハンドラ
    let nativeReqHandler = NativeRequestHandler()


    // 起動画面
    var targetURL = NSBundle.mainBundle().pathForResource("hoge", ofType: "html");

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        // リクエストをキャプチャするのでWebViewのデリゲート設定
        wb.delegate = self

        // 起動画面をリソースに。。。
        let requestURL = NSURL(string: targetURL!)
        let req = NSURLRequest(URL: requestURL!)
        wb.loadRequest(req)

    }


    // まあ、どーでもいい
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // まあ、どーでもいい
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    // WebViewのデリゲートメソッド
    func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {

         // 何かリクエスト来たら 
        // リクエストハンドラに処理を委譲
        // プロトコルで「nativeaction」が来たら、
        // 処理をswift側でやる。
        return self.nativeReqHandler.handleRequest(webView, request: request, naviTp: navigationType)
    }

    func webViewDidFinishLoad(webView: UIWebView) {
        // shouldStartLoadWithRequestでfalseの場合はこのイベントは起きない
//        webView.request!.URL
//        print("*** load completed to:\(webView.request!.URL!)")
    }


}


5.
WebViewのリクストキャプチャするクラスをこんな感じで。。。

import UIKit

class NativeRequestHandler {

    func handleRequest(webView: UIWebView, request: NSURLRequest, naviTp: UIWebViewNavigationType) -> Bool {

        let strUrl = request.mainDocumentURL!.absoluteString
        print("start handle request:\(strUrl)")

        if isNativeRequest(request) {
            // Native処理の場合
            handleNativeRequest(webView, request: request, naviTp: naviTp)
            // 画面遷移なし
            return false
        }

        // その他はコンテンツを画面遷移するようにtrueを返す
        return true
    }

    // nativeaction判定
    func isNativeRequest(request: NSURLRequest) -> Bool {
        // 適当。。。
        if request.URL!.scheme.caseInsensitiveCompare("nativeaction") == NSComparisonResult.OrderedSame ||
            request.URL!.scheme.caseInsensitiveCompare("data") == NSComparisonResult.OrderedSame{
            return true
        }
        return false
    }


    // Getパラメータパース
    func parseQueryString(request: NSURLRequest) -> [String:String] {
        let q = request.URL!.query
        if q == nil {
            let empty: [String:String] = [:]
            return empty
        }

        let prms = request.URL!.query!.componentsSeparatedByString("&")
        var params: [String:String] = [:]

        for sprm: String in prms {
            let prms = sprm.componentsSeparatedByString("=")
            params[prms[0]] = prms[1]
        }
        return params
    }



    // Getパラメータパース
    func parseGetParams(request: NSURLRequest) -> NSDictionary {

        let strUrl = request.mainDocumentURL!.absoluteString
        let urlAttr = strUrl.characters.split{$0 == "?"}.map(String.init)
        let urlParamsStr: String = urlAttr[1]

        let urlParamStrs = urlParamsStr.characters.split{$0 == "&"}.map(String.init)

        let results = NSMutableDictionary()

        for urlParamStr: String in urlParamStrs {

            let keyVal = urlParamStr.characters.split{$0 == "="}.map(String.init)
            results[keyVal[0]] = keyVal[1]
        }

        return NSDictionary(dictionary: results)
    }

    // JSからのデバッグ出力
    func handleDebugLogFromJS(webView: UIWebView, request: NSURLRequest) {

        let strUrl = request.mainDocumentURL!.absoluteString
        let urlAttr = strUrl.characters.split{$0 == "?"}.map(String.init)
        let urlParamsStr: String = urlAttr[1]
        let encMsg = urlParamsStr.stringByReplacingOccurrencesOfString("message=", withString: "")

        let data = NSData(base64EncodedString: encMsg, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
        let logMsg = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String

        print(logMsg)
    }


    // Post判定
    func isPostMethod(request: NSURLRequest) -> Bool {
        let m = request.HTTPMethod
        if (m != nil) {
            if m! == "POST" {
                return true;
            }
        }
        return false
    }




    // アプリ処理呼び出しハンドラ
    func handleNativeRequest(webView: UIWebView, request: NSURLRequest, naviTp: UIWebViewNavigationType) {

        let strUrl = request.mainDocumentURL!.absoluteString

        var pMap = Dictionary<String,String>()
        let method = request.HTTPMethod
        print("method:[\(method)]")

        // BodyからPostパラメータを取り出す
        let data = request.HTTPBody;
        if (data != nil) {
            let bodyStr = NSString(data: data!, encoding: NSUTF8StringEncoding)! as String!

            print("==============")
            let params = bodyStr.characters.split{$0 == "&"}.map(String.init)
            for param in params {
                let pKv  = param.characters.split{$0 == "="}.map(String.init)


                if pKv.count == 1 {
                    pMap[pKv[0]] = ""

                } else {
                    // Postパラメータはエンコードされてるので、デコード
                    var v = pKv[1]
                    let decodeS = (v as NSString!).stringByRemovingPercentEncoding
                    if decodeS != nil {
                        v = decodeS as String!
                    }
                    pMap[pKv[0]] = v
                }
            }
            for (k,v) in pMap {
                print("[\(k)]=[\(v)]")
            }
        }

        // PDF出力指示の場合
        // ここでやる処理じゃないけど、忘備録なので、とりあえず、ここで書く。。。
        if strUrl.hasPrefix("nativeaction://generated_pdf") {
            if pMap["enc_pdf"] != nil {
                let decodedData = NSData(base64EncodedString: pMap["enc_pdf"]!, options: NSDataBase64DecodingOptions())
                if decodedData != nil {

                    // Documentディレクトリを取得
                    let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
                    // ファイル名
                    let fileName = "/ggg.pdf"
                    // 保存する場所
                    let filePath = documentsPath + fileName

//                    print(filePath)

                    decodedData!.writeToFile(filePath, atomically: true)
//                    print("decode")

                    // 画面上のjavascriptをキックし、ローカルに出力したPDFを表示。 
                    // location.hrefしてるだけだが。。。
                    webView.stringByEvaluatingJavaScriptFromString("handleCreatePdfFromiOS('file://\(filePath)');")

                }
            }
        } else {
        // どうしようか。。。
        }
    }

}

いらない処理がたくさん入ってるけど、とりあえずこんな感じで。。。

6.
実行してみる。。。
ios_2.png

ボタンがかぶってるけど、気にしない。。
で、ボタンを押してみる。

ios_3.png

日本語で、PDFが出力された。

・TODO、その他。
1.
PDFをJS作る時、ちょっと考えるので、お待ちくださいが必要。
2.
作った結果をBase64エンコードするので、大きいサイズだとどうなるか。
3.
画像を使う場合、画像データはパスで指定できない。Base64でエンコードしたものを
jsonに設定する。「2.」に関連して、サイズが、どしても、大きくなるのでどーしたものか。。。
でも、テキストベースの軽いPDFならこれで、haruを導入しなくてよいし、
kotolinとあるてーど共通化出来るのでとても助かる。。。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2