Rails5でChart.js入りのPDF出力を行うときにハマったこと


Ruby on Rails 5でPDFダウンロード機能を作成しようとした時に後輩君と1週間近くハマった時の解決策

この問題はRails/wicked_pdf/wkhtmltopdf/Chart.jsの各要素におけるバージョンの問題と、wkthmltopdfとChartjsで生じるバグの様な事象をjavascript側で補完してあげる対策が必要でした。

検索しても問題は様々でこの記事に書くのは解決できたほんの1ケースに過ぎませんが、この組み合わせなら対策できるという事実を備忘録として残しておこうと思います。

環境

私が上手く動作できるのを確認した環境は下記のとおり

CentOS
Rails 5.1.6.1

[gem]
wicked_pdf (1.4.0)
wkhtmltopdf-binary (0.12.4)
 ※wkhtmltopdf-binary-11を使う記事もありますが2019年8月時点では無印のGemでOKです

[javascriptライブラリ]
Char.js 2.7.3

バージョンについて

これがかなり曲者でした。
そもそもwkhtmltopdfはRailsとは独立したプロダクトなのですが、古いバージョンではChart.jsの要であるCanvasに対応していなかったりします。
また、Chart.jsとwkhtmltopdfを組み合わせたときは双方のバージョンによっていたるところでエラーになってしまします。
ちなみに、wkhtmltopdf-binary (0.12.4)とChar.js 1.6.0はNGで、それ以外はChartJS 1.x系か、2.7以降ではOKでした。

wicked_PDFによる実装

これについては多数のサイトがあるので割愛します。下記リンクをお役立てください。
ただし、下記リンクは純なHTMLなので問題ないですがRailsのViewになるとちと問題があります。
手順としては
 1. javascriptを外だしする
 2. wicked_pdf_javascript_include_tagでインクルードする
です

wkhtmltopdf + wicked_pdf + chart.js固有の問題

いくつかあります。

  • View : canvasをdivで囲み、両者にサイズを指定しないとサイズ調整できないし、最悪表示されない
  • Rails : javascript_delayで一定時間待っておかないとグラフ描画が間に合わず何も表示されない

View : canvasをdivで囲み、両者にサイズを指定する

つまりこういう事
囲むDIVタグと囲まれるcanvasタグは同じサイズで間にはpaddingもmarginも入らないようにするのが常套手段のようです。
抜けやすいチェックポイントだと思います

sample.html
<div style="width:500px; height:500px;">
  <canvas id="myChart1" style="width:500px; height:500px;"></canvas>
</div>

Rails : javascript_delayで一定時間グラフの描画を待つ

これはアニメーションしているグラフでは特にシビアな問題の様で、PDF出力対象だけを切り分けられる場合はJSを別にしてanimation: falseにしておくのも予防策としていいかもしれませんね
それを行わなくても下記の様に少し待ってあげれば問題ありません。

sample_controller.rb
    def pdf_output
      respond_to do |format|
        format.html
        format.pdf do
          pdf = render_to_string(
            pdf: 'pdffile',
            encoding: 'UTF-8',
            template: 'sample_pdf.html',
            layout: 'sample.pdf.erb',
            javascript_delay: 1000 # <-ここの時間を調整する
          )
          send_data(pdf, filename: 'file.pdf')
        end
      end
    end

wkhtmltopdfがChart.jsに提供できていないものを提供する

wkhtmltopdfの欠陥を自前のJavascriptで補完しましょうという事です。
ご参考 -> https://stackoverflow.com/questions/42561036/wkhtmltopdf-does-not-render-chart-js-2-5-0-graph/57454387#57454387

上記リンクに書いてある中で必要なのは以下の通り
これを外部ファイルとして作成してwicked_pdf_javascript_include_tagでインクルードします。

sample.js
// wkhtmltopdf 0.12.5 crash fix.
// https://github.com/wkhtmltopdf/wkhtmltopdf/issues/3242#issuecomment-518099192
'use strict';
(function(setLineDash) {
    CanvasRenderingContext2D.prototype.setLineDash = function() {
        if(!arguments[0].length){
            arguments[0] = [1,0];
        }
        // Now, call the original method
        return setLineDash.apply(this, arguments);
    };
})(CanvasRenderingContext2D.prototype.setLineDash);
Function.prototype.bind = Function.prototype.bind || function (thisp) {
    var fn = this;
    return function () {
        return fn.apply(thisp, arguments);
    };
};

これで完了。
全てのグラフがしっかり表示できました

ご参考までに

それでも動かない時などは下記リンクを参考にしてみてください。

以上。

その他Assetに含まれない画像を表示する時はwicked_pdf_image_tagではなく普通のimage_tagを使用する等々、少し詰まりましたが割と簡単に答えが見つかるので割愛いたします

とりあえず出来ませんでしたって事にならなくてほっとしてます