Node.js の svgexport を使って SVG から提案書に使える綺麗な PNG 画像を作成する

## 追記

- svgexport が依存している PhantomJS の開発が終了したようです。  
本記事の内容は参考程度にしていただき、ImageMagick や別のモジュールでの実装をお願いします。
    - [PhantomJS の開発が終了しリポジトリがアーカイブ化された - JSer.info](https://jser.info/2018/06/11/phantomjs-ended/)
- svgexport の処理に Promise は不要だったので修正しました。
- Font Awesome の配布元が更新されていたので変更しました。

---

SVG はレスポンシブデザインのサイトを制作するうえで綺麗にアイコンを表示できるので重宝しています。
今では企業やブランドのロゴも SVG で提供されていることも増えてきました。

そんな便利な SVG ですが Google スライドなどで提案書やドキュメントを作る場合、そのままでは簡単に使用できません。
Illustrator などでひとつずつ PNG に変換されている方もいらっしゃるのではないでしょうか。

今回は [svgexport](https://github.com/shakiba/svgexport) という Node.js のモジュールを使用して、できるだけ簡単に SVG を希望のサイズで PNG に一括変換できる方法を検証してみました。

- 検証環境
    - macOS High Sierra 10.13.6
        - Node.js: v8.11.3
        - npm: 5.6.0
    - Windows 7 Home Premium
        - Node.js: v8.11.3
        - npm: 5.6.0

## svgexport を試す

[svgexport](https://github.com/shakiba/svgexport) はコマンドラインでも使用できます。
詳しくは [svgexport の README.md](https://github.com/shakiba/svgexport#readme) を参照していただければ解ると思いますが、 `npx` コマンドで簡単に試してみます。

- `npx` コマンドはバージョンが5.2以上の npm が必要です。
- サンプルデータは以下のファイルを使用しています。  
[File:Node.js logo.svg - Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Node.js_logo.svg)

```sh
# download-cli を使用して Node.js のロゴをダウンロード
npx download-cli https://upload.wikimedia.org/wikipedia/commons/d/d9/Node.js_logo.svg > Node.js_logo.svg

# SVG の viewBox に対して倍率を指定してエクスポート
npx svgexport Node.js_logo.svg Node.js_logo_1.5x.png 1.5x

# 比率はそのままで指定した横幅（ px ）に合わせて拡大、縮小してエクスポート
npx svgexport Node.js_logo.svg Node.js_logo_320.png 320:
```

上記の他にも細かくオプションの指定はできますが、複数の SVG を一括で変換する場合は JSON ファイルやオブジェクトを使用します。
以降は Node.js の勉強も兼ねて任意のディレクトリの SVG のリスト取得と変換まで試してみます。

## npm install

使用する npm のモジュールをインストールします。

- [svgexport](https://github.com/shakiba/svgexport)  
SVG を PNG や JPG に変換します。
- [yargs](https://github.com/yargs/yargs)  
コマンドラインの引数をパースします。

```sh
# package.json の作成
npm init -y

# モジュールのインストール
npm i svgexport yargs
```

`package.json` に以下の項目が追加されていれば問題ありません。

```json:package.json
{
  "dependencies": {
    "svgexport": "0.3.2",
    "yargs": "11.0.0"
  }
}
```

## svgexport の処理を追加する

`index.js` を新規に作成し以下のコードを記述します。

```js:index.js
const fs = require('fs'); // Node.js のコアモジュール File System を読み込み
const path = require('path'); // Node.js のコアモジュール Path を読み込み
const svgexport = require('svgexport'); // svgexport の読み込み
const { argv } = require('yargs'); // yargs を使用してコマンドの引数をパース
const opt = {
  input: argv._[0] || '', // ディレクトリの指定がない場合は現在のディレクトリを指定
  extension: argv.jpg ? '.jpg' : '.png', // `--jpg` で出力を jpg に変更可
  styles: argv.styles || false, // 背景色など SVG のスタイルを指定
  width: parseInt(argv.width) || parseInt(argv.w) || false, // 画像の横幅
  json: Boolean(argv.json) // 変換対象のリストを JSON として書き出すか
};
const svgs = []; // input, output のパスを格納する配列

/**
 * svgexport で参照する input, output のパスを配列に追加する関数
 * @param {string} input - process.cwd() からファイルまでの相対パス
 */
function storeSvgPath(input) {
  const file = input.replace(/(.*)\.svg$/, `$1${opt.extension}`);
  const styles = opt.styles ? ` ${opt.styles}` : '';
  const width = opt.width ? ` ${opt.width}:` : '';
  const output = `svgexport/${file}${width}${styles}`;
  svgs.push({ input, output });
}

/**
 * 再帰処理関数
 * @param {string} input - SVG が格納されたディレクトリ、または SVG までのパス
 */
function loop(input) {
  const entry = path.resolve(__dirname, input);
  try {
    fs.statSync(entry).isFile()
      ? /.*\.svg$/.test(input) && storeSvgPath(input)
      : fs.readdirSync(entry).forEach(cv => {
          loop(path.join(input, cv));
        });
  } catch (err) {
    console.log(err.toString());
    process.exit(1);
  }
}

// 再帰関数の実行
loop(opt.input);

// JSON の書き出し
opt.json && fs.writeFileSync('datafile.json', JSON.stringify(svgs, '', '\t'));

// svgexport を使った画像の書き出し
svgexport.render(svgs, err => {
  err ? console.log(err.toString()) : console.log('Success');
});
```

SVG の viewBox のサイズ以外でエクスポートしたい場合、比率を変えることはないと思うので任意の横幅（ px 指定）で拡大縮小してエクスポートできるようにしています。

`index.js` を作成後は以下の node コマンドを実行することで `svgexport/` ディレクトリに画像を書き出すことができます。
`<input>` の指定は SVG ファイル、ディレクトリのどちらかになります。

```sh
# node command
node index <input> <options>

# options
#   --jpg       出力する画像の拡張子を .jpg に変更するフラグ
#   --json      datafile.json を書き出すフラグ
#   --width, -w 出力する画像の幅を px 指定
#   --styles    SVG のスタイルを変更

# Example
node index input-directory --json --width 320 --styles "path{fill:#fff;}"
node index input.svg --jpg --width 320 --styles "svg{background:#fff;}"

# 対象の SVG のリストを JSON に書き出し、横幅 320px の白背景の JPG でエクスポート
node index Node.js_logo.svg --jpg --json --width 320 --styles "svg{background:#fff;}"
```

毎回コマンドの入力するのが手間な場合は npm run-script に登録しても良いかと思います。

```json:package.json
"dependencies": {
  "svgexport": "0.3.2",
  "yargs": "12.0.1"
},
"scripts": {
  "export": "node index --json --width"
}
```

```sh
# 上記の package.json の記述の場合は最後に --width がきているので指定する順番に注意
npm run export <image-width> <input>
```

## Font Awesome の SVG を一括で PNG にする

[Font Awesome](https://fontawesome.com/) は Web で使用できるアイコンの中でも特に有名ではないでしょうか。
無料版でも豊富なアイコンが用意されているため提案書にもこのまま使えるようにしたいと思います。

1. [Font Awesome](https://fontawesome.com/) のページから無料版の ZIP ファイルをダウンロードします。
2. バージョン 5.2.0 の場合 `fontawesome-free-5.2.0-web/svgs` に SVG が格納されてるので、 `svgs` を `package.json` と同じ階層に移動します。
3. 以下のコマンドを実行して `svgs` 配下の SVG を PNG 画像に一括で変換します。  

    ```sh
    node index svgs --width 320
    ```

PC のスペックや画像のサイズにもよるかと思いますが、989個の SVG を 320px 幅で PNG に変換した場合、かかった時間は約30秒（ 30494.594ms ）でした。
また、Windows の場合は100個を超えるファイルを一括で処理しようとするとエラーになってしまいました。
環境によっては大丈夫かもしれませんがエラーになってしまう場合はファイルを小分けにして試してみてください。

黒以外の色のアイコンが必要な場合は `--styles` オプションを使用して `<path>` の `fill` 属性の値を変更してください。

```sh
node index svgs --width 320 --styles "path{fill:#f00;}"
```

## 最後に

今回作成したソースは [GitHub](https://github.com/hi85gh/blog-nodejs-svgexport) にプッシュしています。

~~async / await を使ってモダンな処理をしたかったのですが、ちゃんと理解できておらず Promise で記述しています。
色々な記事を参考にしてなんとか再帰処理を実装できましたが、もっといい書き方などコメントいただければ嬉しいです。~~
※svgexport の処理に Promise は不要だったので修正しました。

なお、検証中に [ImageMagick](https://github.com/ImageMagick/ImageMagick) という画像処理ツールを教えていただきました。
Node.js でやるよりも [ImageMagick](https://github.com/ImageMagick/ImageMagick) を使った方が断然早そうでした...

### 参考記事一覧

- [svgexport·SVGを画像やPDFに変換 MOONGIFT](https://www.moongift.jp/2014/10/svgexport-svg%E3%82%92%E7%94%BB%E5%83%8F%E3%82%84pdf%E3%81%AB%E5%A4%89%E6%8F%9B/)
- [node.js　ファイル一覧を取得する - Qiita](https://qiita.com/_shimizu/items/f08eaacdbcdce0204e36)
- [Node.jsを使った簡単なファイル操作 - Qiita](https://qiita.com/wifeofvillon/items/b307306eed08d8a7b4f4)
- [File System | Node.js v9.10.1 Documentation](https://nodejs.org/api/fs.html)
- [Promiseを使った非同期ループ処理の書き方について | hifive開発者ブログ](https://blog.htmlhifive.com/2016/02/22/promise-async-loop/)
- [svgをpngに変換するワンライナー - Qiita](https://qiita.com/suin/items/0bfa07b7c29883beb50d)