jsPDFを使ってKonvaのstage要素をPDF化する際に困った話

全般

こんにちは!話題のELDEN RINGを買ったはいいけどあまり遊べていない竹村です。

現場業務にて、JavaScriptのフレームワークであるKonva.jsとjsPDFを使って、画面に描画されているstage要素をPDF化しようとしたところ、stage要素のスケールも含めてPDF化されてしまい、stage全体がPDF化されない現象が起こったため、その解決方法を書いていきたいと思います。
尚、環境はVue2系となります。

※stageはマウスホイール操作で拡大縮小できるようにしていた為、画面上に描画されているスケール通りにPDFが生成されてしまっていました。

Konva.jsとは?

Konva.jsはJavaScriptのフレームワークで、divタグ内のcanvas要素を作成し、図形の生成や編集、画像の配置等ができます。stageという最下層のレイヤーの上に、複数のlayerを追加して画面描画できます。

座標やスケールを操作する事で、stage上の要素を拡大縮小したり、html2Canvasを使用する事で画像化できたりと、とても汎用性の高いフレームワークとなっています。

jsPDFとは?

JavaScriptのライブラリです。生成されたjsPDFインスタンスに対し、テキストや図形、画像等を追加し、簡単にPDFを作成してくれます。

また、jsPDFインスタンスはarraybufferやblobといった形で出力できるので、base64形式にエンコードしてDBに保存もできたりします。
英語ですが公式ドキュメントも充実していますので、使い勝手の良いライブラリとなっています。

結論から言うと

stage要素全体がPDF化されない件について結論から述べますと、stageのscaleとpositionを初期化する事で解決しました。

まず、pdf化させる関数内でstageを取得します。(関数の引数はselfとしています)

var stage = self.$refs["stage"];
var getStage = stage.getStage();

$refs[“stage”]で取得したstage要素から、getStage()することでstage要素を取得できます。
(最初、$refsで取得した段階でstageが取れたと思っていたのですが、stageをコンソールにログ出力したところ、更にgetStage()する必要があるとわかりました。ややこしい。。。)

stage要素を取得できたところで、scaleとpositionを初期化してあげます。

getStage.setScale({ x: 1, y: 1 });
getStage.setPosition({ x: 0, y: 0 });

scaleは拡大縮小率なので、等倍である1に設定します。
positionは、画面上でstage要素をドラッグで動かしている場合があるので、座標位置を0に戻してあげます。

これで、stage要素を等倍かつ正しくPDF化させることができました。

PDF化関数の全体的なソースは、こんな感じです。

function downloadPdf(self) {
  //stageを取得
  var stage = self.$refs["stage"];
  var getStage = stage.getStage();

  //pdfサイズとなる縦横の幅を取得
  var width = getStage.attrs.width;
  var height = getStage.attrs.height;

  //pdfインスタンスを生成
  var pdf = new jsPDF({
    orientation: "l",
    unit: "px"
    format: [width, height],
    compress: true,
  });

  //現状のscaleとpositionを取得
  var scale = getStage.getScale();
  var position = getStage.getPosition();

  //scaleとpositionを初期化
  getStage.setScale({ x: 1, y: 1 });
  getStage.setPosition({ x: 0, y: 0 });

  //pdf化処理
  var output = getStage.toImage({
    callback(image) {
      //pdfインスタンスにimageを追加
      pdf.addImage(image, 10, 10, width, height);

      //scaleとpositionを元に戻す
      getStage.setScale(scale);
      getStage.setPosition(position);

      //pdfダウンロード
      pdf.save("hoge.pdf");
    },
    mimeType: "image/png",
    x: 10,
    y: 10,
    width: width,
    height: height,
  });
}

解説

7、8行目で縦幅と横幅を取得していますが、PDF出力させたい幅を固定値で入れても問題ありません。例として、ここではstageそのものの縦横としています。

11行目で、jsPDFインスタンスを作成しています。作成する際、引数をいくつか設定しています。

orientation:pdfを出力する際の向き(縦か横か)を指定できます。ここでは、設定値に”l”(landscapeのl)を指定している為、出力される際は横向きになります。
unit:出力する際の単位を指定します。デフォルトはpxなので、わざわざ書かなくても大丈夫です。
format:縦横幅のサイズです。a4やb5などといった指定の仕方もできますし、今回のように配列で直接指定することもできます。
compress:pdfを出力する際、圧縮するかどうかをtrueかfalseで指定できます。自分が試したときは、指定しないとファイルサイズがとんでもないことになったので、trueにすることをお勧めします。。。

19、20行目では、現状のscaleとpositionを取得しています。
一旦保持しておき、これらを初期化した後に33、34行目で元の値に戻しています。

実際のPDF化している部分では、Konva.jsのStageが持つtoImage()メソッドを使用して、stage要素を一旦画像にしたうえで(callback関数の引数imageが画像です)、pdfインスタンスのaddImage()メソッドに渡しています。
ポイントとしては、toImage()メソッドの第一引数がcallback関数の為、その中でpdfインスタンスにimageを渡し、save処理していることです。

終わりに

いかがでしたでしょうか。スケールとポジションを一旦初期化している為、画面上は一瞬stage要素の大きさが変化してしまいますが、理想の形でPDFが作成されるのであれば許容範囲といったところでしょうか。

それでは、皆さんも良きPDFライフを!

コメント