Vue-CLI 3でElectronの開発環境を作る

Vue CLI Plugin Electron Builderを使い、Electron+Vue.jsの開発環境を作る。

Vue-CLI 3をインストールする

$ npm install -g @vue/cli

バージョンが3.xになっていることを確認する。

$ vue --version
3.10.0

新しいプロジェクトを作成する

$ vue create hello-world

プリセットを選択するように求められるので、好みの設定でプロジェクトを作る。

GUIでプロジェクトを作成したい場合は、「vue gui」コマンドを使うこともできる。

$ vue gui

Vue-CLI 3で作成したアプリのディレクトリに移動する

$ cd hello-world

Vue CLI Plugin Electron Builderをインストールする

$ vue add electron-builder

途中で、インストールするElectronのバージョンの問い合わせがあるので、最新のバージョンを選ぶ。

? Choose Electron Version (Use arrow keys)
^4.0.0 
^5.0.0 
❯ ^6.0.0 

開発用サーバーを実行する

$ npm run electron:serve

アプリケーションが起動する。

App.vueを編集すると、変更が即座に反映される。

アプリケーションを再起動する必要はない。

アプリケーションをビルドする

$ npm run electron:build

参考

JavaScriptでPDFファイルの各ページを画像にする

PDF.jsを使って、PDFファイルを読み込み、各ページを画像に変換する。

pdfjs-distのインストール。

$ npm install --save pdfjs-dist

PDFを画像に変換するクラスのソースコード。(pdf2image.js

/* eslint-disable no-await-in-loop */
import PDFJS from 'pdfjs-dist';

class Pdf2Image {
  /**
   * @param {string} pdfUrl PDFのURL
   * @return {Pdf2Image} Pdf2Imageのインスタンスを返す
   */
  static async open(pdfUrl) {
    const pdfDoc = await PDFJS.getDocument({ url: pdfUrl }).promise;
    return new Pdf2Image(pdfDoc);
  }

  /**
   * @param {PDFJS.PDFDocumentProxy} pdfDoc
   */
  constructor(pdfDoc) {
    this.pdfDoc = pdfDoc;
  }

  /**
   * @return {Number} ページ数
   */
  numPages() {
    return this.pdfDoc.numPages;
  }

  /**
   * PDFの指定ページを画像にし、画像のDataUrlを返す
   * @param {Number} pageNo ページ番号(1〜)
   * @param {Object} option
   *                  {scale:画像の倍率}, 画像を指定した倍率で拡大する
   *                  {width:最大幅, height:最大高さ} 画像を指定した領域に収まるサイズにする
   *                  {image:'jpeg|webp|png|'} 画像フォーマット
   * @return {String} ページ画像のDataUrl
   */
  async getImageDataUrl(pageNo, option) {
    const page = await this.pdfDoc.getPage(pageNo);
    const scale = Pdf2Image.calcScale(page, option);
    const viewport = page.getViewport({ scale });
    const canvas = document.createElement('canvas');
    const canvasContext = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    canvasContext.height = viewport.height;
    canvasContext.width = viewport.width;

    const renderContext = {
      canvasContext,
      viewport,
    };
    await page.render(renderContext).promise;
    switch (option.image) {
      case 'jpeg':
        return canvas.toDataURL('image/jpeg');
      case 'webp':
        return canvas.toDataURL('image/webp');
      default:
        return canvas.toDataURL();
    }
  }

  /**
   *
   * @param {PDFJS.PDFPageProxy} page
   * @param {Object} option
   *                  {scale:画像の倍率}, 画像を指定した倍率で拡大する
   *                  {width:最大幅, height:最大高さ} 画像を指定した領域に収まるサイズにする
   * @return {Number} 倍率
   */
  static calcScale(page, option) {
    if (option.scale !== undefined) {
      return option.scale;
    }
    if (option.width === undefined || option.height === undefined) {
      return 1.0;
    }
    const viewport = page.getViewport({ scale: 1.0 });
    return Math.min(option.width / viewport.width, option.height / viewport.height);
  }

  /**
   * PDFのすべてのページを画像にし、画像のDataUrlを返す
   * @param {Object} option
   *                  {scale:画像の倍率}, 画像を指定した倍率で拡大する
   *                  {width:最大幅, height:最大高さ} 画像を指定した領域に収まるサイズにする
   * @return {String[]} ページ画像のDataUrl
   */
  async getAllImageDataUrl(option) {
    const pages = [];
    const numPages = this.numPages();
    for (let i = 1; i <= numPages; i += 1) {
      const img = await this.getImageDataUrl(i, option);
      pages.push(img);
    }
    return pages;
  }
}

export default Pdf2Image;

使用例(App.vue

<template>
  <div id="app">
    <div>
      <label>
        PDFファイルを選択
        <input type="file" accept="application/pdf" v-on:change="onFileChange">
      </label>
    </div>
    <div v-show="isLoading">読込中</div>
    <div v-for="image in images" v-bind:key="image">
      <img v-bind:src="image">
    </div>
  </div>
</template>

<script>
import Pdf2Image from './pdf2image';

export default {
  name: 'app',
  data() {
    return {
      isLoading: false,
      images: [],
    };
  },
  methods: {
    async onFileChange(e) {
      this.isLoading = true;
      try {
        const file = e.target.files[0];
        const url = URL.createObjectURL(file);
        const pdf2image = await Pdf2Image.open(url);
        // const images = await pdf2image.getAllImageDataUrl({scale:2.0});
        const images = await pdf2image.getAllImageDataUrl({ width: 400, height: 400 });
        this.images = images;
      } catch (error) {
        console.log(error);
      }
      this.isLoading = false;
    },
  },
};
</script>

大きな画像をA4用紙1枚に入れて印刷するCSS

そのまま印刷するとA4用紙1枚に入り切らない大きな画像がある。

この画像をA4用紙に入れて印刷する。

HTMLファイルとCSSファイルは次のようになる。

HTMLファイル(landscape.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" media="all" href="print.css" />
</head>
<body>
    <div class="content-print content-print-landscape">
        <img src="1600x1200.jpg" />
    </div>
</body>
</html>

print.css

@media print {
    @page {
        size: A4 landscape;
        margin: 0;
    }
    body {
        height: 200mm;
        width: 287mm;
    }
    /* 印刷したくない領域 */
    .hidden-print {
        display: none;
    }
    /* 印刷したい領域 */
    .content-print-landscape {
        top: 50%;
        left: 50%;
        height: 200mm;
        width: 287mm;
    }
    .content-print-landscape img {
        max-width: 100%;
        max-height: 100%;
        width: auto;
        height: auto;
    }
}

このCSSは、用紙をA4横にしているため、画像が縦長のときは大きな余白ができる。

画像が縦向きのときは、画像を回転して印刷する。

HTMLファイル(portrait.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" media="all" href="print.css" />
</head>
<body>
    <div class="content-print content-print-portrait">
        <img src="1200x1600.jpg" />
    </div>
</body>
</html>

CSSファイル

@media print {
    @page {
        size: A4 landscape;
        margin: 0;
    }
    body {
        height: 200mm;
        width: 287mm;
    }
    /* 印刷したくない領域 */
    .hidden-print {
        display: none;
    }
    /* 印刷したい領域 */
    .content-print-landscape {
        top: 50%;
        left: 50%;
        height: 200mm;
        width: 287mm;
    }
    .content-print-landscape img {
        max-width: 100%;
        max-height: 100%;
        width: auto;
        height: auto;
    }
    .content-print-portrait {
        height: 287mm;
        width: 200mm;
        transform-origin: 100mm 100mm;
        transform: rotate(270deg);
    }
    .content-print-portrait img {
        max-width: 100%;
        max-height: 100%;
        width: auto;
        height: auto;
    }
}

後は、画像のサイズに合わせてスタイルに「content-print-landscape」か「content-print-portrait」を適用すればいい。

JavaScriptでPDFファイルから画像を抽出する

JavaScriptでPDFファイルから画像を抽出するプログラムを書いた。

ソースコードはこちら。

PDFファイルの処理には、pdf.jsを使用した。