HugoとVivliostyleでMarkdownから書籍品質のPDFを出力できるドキュメントサイトを作ろう【Hugoテーマ紹介】

昨今Markdownベースでドキュメントサイトを構築することが増えています。Markdownでサクッと書いてWebで公開。バージョン管理もできてとても良い感じです。しかしサイトのコンテンツを書籍のような組版文書にしようとすると途端にハードルが高くなります。

## 映えるPDFを作りたい

見映えを気にしないでPDFを作りたいだけなら、ブラウザの印刷機能を利用できます。しかし、表紙・目次・ページ番号付きで、**書籍や論文や取説や仕様書に相応しいレベルのPDF**となるとどうでしょう。なかなか一筋縄ではいきませんよね。職場で仕様書や簡易取説・手順書などをバージョン管理しやすいMarkdownで書きたくても、綺麗なPDFを出力する手段がなく、諦めてWordを使う経験をしたことのある方も多いのではないでしょうか。

## Vivliostyle

そんな悩みを解決してくれる素晴らしいプロダクトがあります。[Vivliostyle](https://vivliostyle.org/ja/)です。Vivliostyleは、Markdownやhtmlを元に、[css組版](https://vivliostyle.github.io/vivliostyle_doc/ja/vivliostyle-user-group-vol5/content/shinyu/index.html)によって書籍品質のPDFを出力できるOSSです。コマンドライン版の[Vivliostyle CLI](https://docs.vivliostyle.org/ja/vivliostyle-cli)も提供されており、元となる原稿群とconfigファイルを準備すればPDF組版文書が出力できます。ドキュメントサイトの生成時にこのconfigファイルを一緒に生成することができれば、VivliostyleによるPDF化で高品質な文書を出力できるということです。

## HugoからVivliostyleのconfigを出力する

[Hugo](https://gohugo.io/)はMarkdownからWebサイトを生成できる静的サイトジェネレータで、`The world’s fastest framework for building websites` の謳い文句の通り、非常に高速にドキュメントサイトをビルドできます。テーマ作成により独自テンプレート出力が可能なので、これによりVivliostyleにインプットするconfigファイルを適切に出力すれば
```mermaid
flowchart LR
    執筆 --> Hugoでhtml/config出力 --> VivliostyleでPDF出力
```
のように、執筆からPDF生成までシームレスに統合できるはずです。

## hugo-theme-vivliocli

これらのアイデアから、ドキュメントサイトとともにVivliostyle用のconfigファイルを出力するHugoテーマ、 [hugo-theme-vivliocli](https://github.com/mochimochiki/hugo-theme-vivliocli) を作りました。テーマの詳しい使い方は [User Guide](https://mochimochiki.github.io/hugo-theme-vivliocli/ja/) を参照してください。出力例でもあるPDF版は[こちら](https://mochimochiki.github.io/hugo-theme-vivliocli/ja/UserGuide.pdf)です。以下、簡単に機能を紹介します。

### 表紙・目次・しおり・章番号・ページ番号つき組版PDFを任意ディレクトリから出力

![2023-05-02_23h35_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101002/5fb02804-b89c-cde7-4955-171fb92316bb.png)
`_pdf.md`ファイルが配置された任意のセクションを、表紙・目次・しおり・章番号・ページ番号を含む組版PDFとして出力できます。章番号はMarkdownファイルの階層構造に加えファイル内の見出しまで考慮して自動で割り当てられます。1つのサイトに複数の`_pdf.md`を置くこともできるため、複数のPDF文書からなるドキュメントサイトの構築が可能です。

### セル結合を含む複雑な表の描画

![2023-05-02_23h33_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101002/ba7fb137-352e-f624-8e31-f529ef7ce3ed.png)
csvファイルを読み込む形でMarkdown内に表を描画できます。csvの各セル内部でもMarkdown記法を使用できます。また独自記法（`||`, `->`）によりセルの縦横結合も可能です。表のキャプションやテキスト整列などにも対応しています。

### MermaidとMathjaxをサポート

Mermaidによるダイアグラム描画とMathjaxによる数式描画が可能です（PDF化可能なのは後述のバッチ使用時）。
```mermaid
flowchart LR
    A-->B-->C
```
```math
\frac{\pi}{2} =
\left( \int_{0}^{\infty} \frac{\sin x}{\sqrt{x}} dx \right)^2 =
\sum_{k=0}^{\infty} \frac{(2k)!}{2^{2k}(k!)^2} \frac{1}{2k+1} =
\prod_{k=1}^{\infty} \frac{4k^2}{4k^2 - 1}
```

## 使い道

GitHubPagesでドキュメント公開しつつPDFも用意したり、個人ブログをテーマごとにPDF出力してムック本や技術書にしてみたり、仕様書、取説などプロダクトに関連する一連のPDFを1サイトで管理運営したり、色々考えられます。因みに筆者はまだPDFを実際に入稿したことはないので、このテーマで出力したPDFを入稿して紙の書籍化する場合のハードルは未検討です。

## 実際に使ってみる

Hugo, Vivliostyle, hugo-theme-vivliocli を使って実際にPDFを作成してみましょう。筆者の環境がWindowsなのでコードはWindowsをベースに記載します。

### HugoとVivliostyleのインストール

[こちら](https://gohugo.io/installation/)からHugoをインストールします。Windowsの場合以下のようにwingetからインストールできます。

```batch
winget install Hugo.Hugo
```

次にVivliostyleをインストールします。[Node.js](https://nodejs.org/ja)環境がない場合は先にインストールしておいてください。

```batch
npm install -g @vivliostyle/cli
```

### サイトを作成する

Hugoで新しいサイトを作成します。

```batch
hugo new site myPDF
cd myPDF
```

[こちら](https://github.com/mochimochiki/hugo-theme-vivliocli/releases)からダウンロードした`Source code (zip)`を展開して`/themes/hugo-theme-vivliocli`となるように適宜リネームして配置します。また、`/themes/hugo-theme-vivliocli/exampleSite/config`ディレクトリを`/config`にコピペします。

### configの編集

`/config/default.toml`を開き以下の行を削除しましょう。
```toml
themesdir = "../.." 
```
また、baseURL,titleを編集します。
```toml
baseURL = ""
title = "My PDF Site"
```

### PDF表紙を作る

`/content/ja/firstpdf`ディレクトリを作成し、`/content/ja/firstpdf/_pdf.md`を作成します。これがPDFの表紙兼設定ファイルになります。内容は見たらなんとなく分かるかと思います。詳しくは[こちら](https://mochimochiki.github.io/hugo-theme-vivliocli/ja/Manual/pdfconfig.html)。

```md:_pdf.md
---
pdfname: 'firstpdf'
doctitle: 'My first pdf'
subtitle: 'subtitle'
header: '2023/5/2'
author: 'auther'
pagesize: 'A5'
book: true
cover: true
toc: true
sectionNumberLevel: 2
colophon: false
outputs:
- vivlio_cover
- vivlio_config
---
```

### コンテンツを作る

`/content/ja/firstpdf/_index.md`を作成して以下のように編集します。これはサイトのチャプターページとなります。PDF出力時には含まれないので適当にタイトルだけつけておきます（ちなみにサイト上ではweight順にメニューに表示されます）。

```md:_index.md
---
title: "firstpdf"
weight: 10
---
```

次に`/content/ja/firstpdf`下に`first.md`,`second.md`を作成して記事を書きましょう。Hugoではフロントマターの`title`がトップレベルの見出しになるので、Markdown的には見出し2（##）以下を本文として書くことになります。

```md:first.md
---
title: "最初の記事"
weight: 10
---

## 最初の記事

こんにちは。これは最初の記事です。
```

書けたらHugoでプレビューしてみましょう。

```bash
hugo server --config config/default.toml
```

`http://localhost:1313`にアクセスすればサイトプレビューを閲覧できます。適当に記事を追加したり編集・保存したりしてみてください。Hugoのライブリロード機能によりリアルタイムに編集した内容が反映されると思います。

### セル結合した表を挿入してみる

以下のcsvを`first.md`の横に`test.csv`という名前で配置し、`first.md`からincludeしてみましょう。

```csv:test.csv
A,B,C,D
A-1,B-1,C-1,D-1
||,B-2,C-2,**D-2**
```

```md:first.md
## includeしてみる

{{< include "./test.csv" >}}

```

### サイトを出力する

以下のコマンドでサイトをビルドします。

```batch
hugo --config config/default.toml
```

`public`ディレクトリにサイトが出力されます。ドキュメントサイトとしてホストする場合にはこのディレクトリを成果物としてアップします。ローカル環境でも`/public/ja/index.html`をブラウザで開くことで閲覧できます。

### PDFを出力する

PDF出力時には`config.toml`の`isPDF`フラグをtrueに設定しておく必要があります。`default.toml`を書き換えてもよいのですが、環境変数で一時的に上書きしてビルドしてみましょう。

```bat
set HUGO_PARAMS_ISPDF=true
hugo --config config/default.toml
```

同じく`public`ディレクトリにWebサイトが出力されます。`/public/ja/index.html`を開くとメニューが消えて目次になっているのがわかるかと思います。また`/public/ja/firstpdf`には`_pdf.vivlio.config.js`と`_pdf.vivlio.cover.html`が出力されています。これらが`_pdf.md`を元に出力されたVivliostyle用configファイルと表紙ファイルです。実装の都合上この場所でそのままビルドができないので、1階層上にコピーし、`/public/ja/_pdf.vivlio.config.js`と`/public/ja/_pdf.vivlio.cover.html`という状態にしてください。
これで準備が整ったのでVivliostyleでビルドしてみましょう。

```bat
cd public/ja
vivliostyle build -c _pdf.vivlio.config.js
```

`/public/ja/firstpdf.pdf`が出力されたら成功です。実際に使用する際には毎回これらの操作をするのは大変なので、スクリプトを用意することになります。Windows限定ですが、テーマの`CI`ディレクトリにはサイトのビルドからVivliostyleによるPDF出力まで実施するバッチ/powershellスクリプトをつけています（数式とMermaid図描画のための中間処理などもやっています）。

## さいごに

本記事ではHugoを扱いましたが、Hugoに限らず様々な静的サイトジェネレータで同じような仕組みにより組版PDF文書が出力可能なサイトを作れるのではないかと思います。Markdownで書いて、ドキュメントサイトと美しいPDFを同時に手に入れましょう！