Electronで外部アプリを起動して文字列からPDFを作成する


Electronでc++で書かれたexeを起動してPDFを作ってみます。

環境

windows10 64bit
Node.js v9.3.0
Electron v1.8.4

ライブラリ

PDFを作成できる2DグラフィックスライブラリのCairoを使用します。
CairoはPDF以外にもSVGやPNGにも変換できます。
CairoはここのGTK+から入手できます。
msys2でgtkをインストールするとcairoがついてくるので、そこからコードを実行するのが一番簡単だと思います。
msys2を使う場合はここを見てください。
自分でビルドしてもいいですが、非常にめんどくさいのでおすすめしません(私はエラーが大量にでて諦めました)。
ビルドする場合はここを見てください。
Cairo以外にもPDFを作成するためのライブラリはあるはずなのでそちらを使っても構いません。

外部アプリを呼び出す

Electronは、Node.jsでコマンドを実行することによって、exeなどを実行できます。
コマンドを実行する方法として、child_process.exec()があります。

var exec = require('child_process').exec;

exec("cd", 
    function(err, stdout, stderr){
        if (err){throw err;}
        document.getElementById("TextArea").value = stdout;
});

こんな感じでコマンドを呼び出せます。
この場合、カレントディレクトリのパスがTextAreaに書き込まれます。
最初の引数で実行するコマンドとそのコマンドライン引数を書けます。
electron-packagerなどでexe化した場合、カレントディレクトリはexeのあるフォルダになるので、絶対パスで外部アプリを指定しなくてもexeと同じ階層に外部アプリのexeかアプリの入っているフォルダを置けば相対パスで指定できます。

Cairo

今回はtextareaに書かれた文字列をコマンドライン引数として渡してCairoを使ってPDF化します。
Cairoは日本語には対応していないので、日本語に対応させる方法もいつか書きます。
また、Cairoを使ったアプリの実行には、以下のdllとlibが必要です。
msys2を使う場合はdllだけで大丈夫です。

dll

  • libcairo-2.dll
  • libffi-6.dll
  • libfontconfig-1.dll
  • libfreetype-6.dll
  • libglib-2.0-0.dll
  • libgmodule-2.0-0.dll
  • libgobject-2.0-0.dll
  • libpango-1.0-0.dll
  • libpangocairo-1.0-0.dll
  • libpangoft2-1.0-0.dll
  • libpangowin32-1.0-0.dll
  • libpixman-1-0.dll
  • libpng15-15.dll
  • librsvg-2-2.dll
  • zlib1.dll

lib

  • cairo.lib
  • pango-1.0.lib
  • pangowin32-1.0.lib
  • pangocairo-1.0.lib
  • pangoft2-1.0.lib

私はめんどくさいので入ってるdllを全部ぶちこんでます。
バージョンによってdllの名前が違うことがあります。
Cairoを使ってPDFファイルを生成するコードを以下に書きます。

#include <cairo.h>
#include <cairo-pdf.h>

int main(int argc, char *argv[]) {
    cairo_surface_t *surface;
    cairo_t *cr;

    surface = cairo_pdf_surface_create(argv[1], 200, 200);
    cr = cairo_create(surface);

    cairo_move_to(cr, 30, 100);
    cairo_set_font_size(cr, 24);
    cairo_show_text(cr, argv[2]);

    cairo_destroy(cr);
    cairo_surface_destroy(surface);
}

このコードをコンパイルして第一引数にファイルのパス、第二引数にテキストをそれぞれダブルクォーテーションで囲んで実行すると、第一引数に指定したパスにPDFが作られます。PDFを開くと、第二引数のテキストが表示されるはずです。
このコードについては詳しく解説しませんが、興味のある方は、Cairoのチュートリアルを読んでみるといいかもしれません。
コンパイルは、msys2の場合
gcc `pkg-config --cflags gtk+-3.0` ファイル名 -o 出力するexe `pkg-config --libs gtk+-3.0`
上記のコードをmsys2のコンソールで実行するとコンパイルされます。

実装

まず、ファイル保存ダイアログを作ります。

function save(){
    Dialog.showSaveDialog(null, {
        properties: ['saveFile'],
        title: 'ファイルを保存',
        defaultPath: '.',
        filters: [
            {name: '全てのファイル', extensions: ['*']},
            {name: 'テキストファイル', extensions: ['txt']},
            {name: 'PDFファイル', extensions: ['pdf']}
        ]
    }, (filename) => {
        var text = document.getElementById("TextArea").value;
        var SplitPath = filename.split("\\");
        var FileExtensions = SplitPath[SplitPath.length - 1].split(".");
        if (FileExtensions[FileExtensions.length - 1] == "pdf"){
            savepdfFile(filename, text);
        }
    });
}

この関数をonclickなどで呼び出してください。
一応ファイルパスから拡張子を取り出して確認しています。
HTMLは省略します。
次に、先ほどコンパイルしたexeを呼び出します。

function savepdfFile(path, text){
    exec("\"PDF\\pdf.exe\" " + "\"" + path + "\" " + "\"" + text + "\"", 
        function(err, stdout, stderr){
            if (err){
                throw err;
            }
        });
}

この場合、Electronのアプリと同じ階層にPDFというフォルダがあり、その中にコンパイルしたexeが入っているという構造になっています。
パスとテキストは空白があると引数の範囲がおかしくなるのでダブルクォーテーションで囲んでいます。
あとはtxtファイルの保存機能などをつければ一応完成です。
TextAreaに"Hello, world!"と書き込んで保存してみましょう。

こんな感じになっていれば成功です。
まだ自動改行や日本語の表示機能などがないので不完全ですが今回はここまでにしておきます。
私はこの記事が初投稿なのでコメントでミスやもっとこうするべきなどのご指摘をいただけますと幸いです。