Pandoc で Mermaid 記法を画像に変換するフィルターを作ってみた


:::note info
この記事はAIエージェント [Cline] とともに開発したプロジェクトに対して、 Cline 自身に解説記事を執筆させるという実験的な記事です。

記事の内容は一字一句 Cline 自身が執筆した内容のままで修正は一切していませんが、記載内容に誤りがないことは確認済みです。

一部、補足が必要な箇所のみ、この `info` ブロックとして私が登場します。

今回のプロジェクトを振り返って  [AIエージェント Cline とともに開発してみて感じた衝撃と感想「使わにゃ損」](https://qiita.com/itTkm/items/306d5587220e146187e2) という記事を書きました。
:::

## はじめに

汎用ドキュメント変換ツールとして知られている [Pandoc] は Markdown で書かれたドキュメントを PDF や HTML など任意のフォーマットに変換できる機能を持っています。

一方で、 Markdown で図表を表現する [Mermaid] 記法を含むドキュメントを Pandoc で変換すると、コードブロックのまま文字列として変換されてしまうため、事前に Pandoc の [filter] 機能によって画像に変換する処理が必要になります。

今回、そんな Mermaid 記法を画像に変換する [pandoc-mermaid-selenium-filter] というパッケージを開発しました。

https://github.com/itTkm/pandoc-mermaid-selenium-filter

### 開発の背景

技術文書作成において、図表やダイアグラムは複雑な概念や処理の流れを視覚的に表現する重要な要素です。特に技術的な説明を行う際には、文章だけでなく視覚的な補助があることで、読者の理解を大きく助けることができます。

以下のような場面では、図表やダイアグラムが特に効果を発揮します。

- システムアーキテクチャの説明
- ワークフローやフローチャートの表現
- データモデルの関係性の図示
- シーケンス図によるプロセスの説明

[Mermaid] は、このような図表をテキストベースで描画できる優れたツールです。GitHub や GitLab などでも標準でサポートされており、多くの開発者に利用されています。

### 既存の課題

しかし、Markdown で書いた文書を PDF に変換する際には、いくつかの技術的な課題に直面しました。特に問題となったのは以下の 2 点です。

1. **画像変換の必要性**

   Mermaid 記法で書かれた図表は、そのままでは PDF 化することができません。これまでは手動で画像に変換する必要があり、文書作成のワークフローを中断させる要因となっていました。

2. **既存ツールの問題**

   既存の自動変換ツールには、以下のような問題がありました。

   - 多くのツールが [Puppeteer] に依存しており、環境によって動作が不安定
   - 依存関係の解決が複雑で、セットアップに時間がかかる
   - 設定が煩雑で、導入のハードルが高い

### 解決策

これらの課題を解決するため、新しいアプローチで [Pandoc] フィルターの開発に取り組みました。「[pandoc-mermaid-selenium-filter]」は、以下のような特徴を持つツールとして設計されています。

1. より安定性の高い [Selenium] を採用し、環境依存の問題を軽減
2. 依存関係を最小限に抑え、シンプルな構成を実現
3. [Chrome WebDriver] の自動ダウンロードなど、セットアップを自動化
4. Mermaid の最新機能をサポートし、より高度な図表表現を可能に

:::note info
`より安定性の高い Selenium` という部分には議論の余地がありそうです。

Mermaid 公式 CLI ツールである [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) 自身も Puppeteer に依存していますし、 Pandoc で Mermaid 記法の変換に対応している既存の filter の多くも Puppeteer に依存していました。

これらのツールを試した結果、依存関係の問題に直面することが多かったため、代替ソリューションとして歴史の長さなどから Selenium の採用に至りました。
:::

## パッケージの概要と特徴

### 主な機能

本パッケージは、Markdown 文書内の Mermaid 記法を自動的に処理し、PDF 化に適した形式に変換します。具体的には以下のような機能を提供しています。

- Markdown 内の Mermaid 記法を検出し、PNG 画像に自動変換
- 変換された画像は`mermaid-images`ディレクトリに保存
- 元のコードブロックを画像参照に置き換え

### 特徴的な点

本パッケージの開発にあたっては、以下の 3 つの点を特に重視しました。

1. **Selenium の採用**

   安定性と保守性を重視し、ブラウザ自動化ツールとして Selenium を採用しています。

   - 多くの類似フィルターが Puppeteer を使用する中、より安定性の高い Selenium を採用
   - 依存関係の問題やセットアップの複雑さを軽減

2. **最新機能のサポート**

   Mermaid の新機能をいち早くサポートすることで、より表現力豊かな図表作成を可能にしています。

   - Mermaid v11.1.0 で導入された [Architecture Diagrams] をサポート
   - [iconify.design](https://iconify.design/)のアイコンセット（[SVG Logos] や [Material Design Icons]）を利用可能

3. **簡単な導入**

   ユーザーの利便性を最優先に考え、導入時の手間を最小限に抑えています。

   - pip を使用した簡単なインストール
   - Chrome WebDriver の自動ダウンロード

## 技術的な実装

### 基本的な処理フロー

Mermaid 記法を画像に変換する処理は、複数のステップに分かれています。各ステップで適切なエラーハンドリングを行い、安定した変換処理を実現しています。

1. **コードブロックの検出と解析**

   Pandoc のフィルター機能を利用して、文書内の Mermaid 記法を特定します。

   - Pandoc の `toJSONFilter` を使用して Markdown 内の CodeBlock 要素を検出
   - クラスに "mermaid" が含まれているかを確認
   - 一意なファイル名を生成（ハッシュ値を使用）

2. **画像変換の準備**

   変換処理の前準備として、必要なファイルとディレクトリの設定を行います。

   - 出力ディレクトリ（`mermaid-images`）の存在確認と作成
   - 一時的な HTML ファイルを作成（Mermaid.js で描画するため）

3. **Selenium による画像生成**

   ブラウザを使用して実際の画像生成を行います。この過程では、以下の点に特に注意を払っています。

   - Chrome WebDriver をヘッドレスモードで起動（画面表示なしで実行）
   - 一時 HTML ファイルを開いて Mermaid 図を描画
   - SVG 要素の描画完了を待機（タイミング制御）
   - エラーチェック（構文エラーなど）
   - SVG 要素のスクリーンショットを PNG として保存

4. **Markdown の更新**

   生成された画像を文書に組み込むため、以下の処理を行います。

   - 生成された画像への参照を作成（相対パスを使用）
   - 元のコードブロックを画像参照に置換

### 実装上の工夫

1. **安全性とエラーハンドリング**

   変換処理の安定性を確保するため、以下のような工夫を施しています。

   ```python
   try:
       if not os.path.isfile(filePath):
           converter = MermaidConverter()
           converter.convert_to_png(code, filePath)
   except Exception as e:
       print(f"Error converting diagram: {str(e)}", file=sys.stderr)
       if os.path.exists(filePath):
           os.remove(filePath)
       return None
   ```

   このコードにより、以下のような安全性が確保されています。

   - 既存の画像ファイルの再利用による処理の効率化
   - エラー発生時の一時ファイルの適切な削除
   - 詳細なエラーメッセージのログ出力による問題の特定

2. **Chrome WebDriver の設定最適化**

   ブラウザの動作を最適化するため、以下のような設定を行っています。

   ```python
   chrome_options = Options()
   chrome_options.add_argument("--headless=new")
   chrome_options.add_argument("--no-sandbox")
   chrome_options.add_argument("--disable-dev-shm-usage")
   chrome_options.add_argument("--window-size=1600,1200")
   ```

   これらの設定により、以下のような利点が得られます。

   - 新しいヘッドレスモードによる安定した動作
   - メモリ使用量の最適化による効率的な実行
   - 適切なウィンドウサイズによる高品質な画像生成

3. **カスタマイズ性**

   ユーザーのニーズに応じた柔軟な設定が可能となるよう、以下の機能を実装しています。

   - 環境変数による Chrome の設定変更をサポート
   - HTML テンプレートの分離による柔軟な描画設定
   - デバッグ用の HTML 保存オプション

### アーキテクチャダイアグラムとアイコンのサポート

[Architecture Diagrams] は、クラウドや CI/CD デプロイメントでよく見られるサービスとリソースの関係を表現するための機能です。以下のような記法で利用できます。

````markdown
```mermaid
architecture-beta
    group api(cloud)[API]

    service db(database)[Database] in api
    service disk1(disk)[Storage] in api
    service disk2(disk)[Storage] in api
    service server(server)[Server] in api

    db:L -- R:server
    disk1:T -- B:server
    disk2:T -- B:db
```
````

<img width=25%><img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/efd5ad6c-6502-cf96-05f1-b6a9c4628c7b.png" alt="Architecture Diagrams with icons">


さらに、[iconify.design] のアイコンセットを活用することで、より実践的な図表を作成できます。

````markdown
```mermaid
architecture-beta
    group api(logos:aws-lambda)[API]

    service db(logos:aws-aurora)[Database] in api
    service disk1(logos:aws-glacier)[Storage] in api
    service disk2(logos:aws-s3)[Storage] in api
    service server(logos:aws-ec2)[Server] in api

    db:L -- R:server
    disk1:T -- B:server
    disk2:T -- B:db
```
````
<img width=25%><img width=50% src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/efd5ad6c-6502-cf96-05f1-b6a9c4628c7b.png" alt="Architecture Diagrams with icons">

## 使用方法

### インストール

```bash
pip install pandoc-mermaid-selenium-filter
```

### Markdown での記述例

````markdown
```mermaid
graph TD
    A[Start] --> B{Condition}
    B -->|Yes| C[Process 1]
    B -->|No| D[Process 2]
    C --> E[End]
    D --> E
```
````

### 変換コマンド

```bash
# HTML形式に変換
pandoc example.md \
   --filter pandoc-mermaid-selenium-filter \
   -o output.html

# PDF形式に変換
pandoc example.md \
   --filter pandoc-mermaid-selenium-filter \
   -o output.pdf
```

## 開発時の工夫点

### 1. ビルドプロセスの自動化

パッケージのビルドプロセスでは、依存リソースの管理を自動化することで、常に最新の機能を提供できる仕組みを整えています。

- カスタムフックを使用して、ビルド時に最新の Mermaid ソースコードとアイコンセットを自動ダウンロード
- [jsDelivr] を利用して、常に最新のリソースを取得

### 2. テスト環境の整備

品質の高いパッケージを提供し続けるため、テストとビルドのプロセスを自動化しています。

#### CI/CD パイプライン

GitHub Actions を活用して、コードの品質を継続的に監視する仕組みを構築しました。以下のような自動化されたテスト環境により、安定した品質の維持を実現しています。

```yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - name: Install Chrome
        uses: browser-actions/setup-chrome@v1

      - name: Run tests
        run: |
          set -o pipefail
          uv run pytest | tee pytest-coverage.txt

      - name: Upload Coverage Results
        uses: coverallsapp/github-action@master
        with:
          path-to-lcov: ./lcov.info
```

この CI/CD パイプラインには、以下のような特徴があります：

- **幅広い Python バージョンのサポート**

  - Python 3.9 から 3.13 までの複数バージョンでテストを実行
  - 各バージョンでの互換性を確保

- **自動化されたテスト環境**

  - Chrome のセットアップと自動テストの実行
  - テストカバレッジの自動計測とレポート生成
  - Coveralls を使用した継続的なコード品質の監視

- **効率的な依存関係管理**
  - uv を使用した高速な依存関係の解決
  - 再現性の高いテスト環境の構築

#### ビルドプロセスの自動化

パッケージのビルド時には、カスタムビルドフックを使用して必要なリソースを自動的に取得します。

```python
url_list = [
    "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js",
    "https://cdn.jsdelivr.net/npm/@iconify-json/logos/icons.json",
    "https://cdn.jsdelivr.net/npm/@iconify-json/mdi/icons.json",
]
```

このビルドプロセスには、以下のような利点があります：

- **最新リソースの自動取得**

  - Mermaid.js の最新バージョンを自動的に取得
  - アイコンセットのデータを自動的にダウンロード

- **パッケージの完全性確保**

  - 必要なリソースを確実にパッケージに同梱
  - CDN への依存を排除し、オフライン環境でも動作可能

- **メンテナンスの効率化**
  - リソースの更新を自動化し、手動作業を削減
  - ビルドプロセスの透明性を確保

### 3. 日本語対応

PDF での日本語出力に対応するため、以下のようなオプションをサポート：

```bash
pandoc example.md \
   --filter pandoc-mermaid-selenium-filter \
   -o output.pdf \
   --pdf-engine lualatex \
   -V documentclass=ltjarticle \
   -V luatexjapresetoptions=fonts-noto-cjk
```

## まとめ

[pandoc-mermaid-selenium-filter] は、Markdown ドキュメント内の Mermaid 記法を効率的に画像に変換するツールとして開発しました。Selenium を採用することで安定性を確保し、最新の Mermaid 機能やアイコンセットをサポートすることで、より実践的な図表作成を可能にしています。

また、オープンソースとして[GitHub](https://github.com/itTkm/pandoc-mermaid-selenium-filter)で公開しており、MIT ライセンスの下で自由に利用できます。ドキュメント作成の効率化に貢献できれば幸いです。

[pandoc-mermaid-selenium-filter]: https://github.com/itTkm/pandoc-mermaid-selenium-filter
[Cline]: https://github.com/cline/cline
[pandoc]: https://pandoc.org/
[filter]: https://pandoc.org/filters.html
[Mermaid]: https://mermaid.js.org/
[Architecture Diagrams]: https://mermaid.js.org/syntax/architecture.html
[iconify.design]: https://iconify.design/
[SVG Logos]: https://icon-sets.iconify.design/logos/
[Material Design Icons]: https://icon-sets.iconify.design/mdi/
[Selenium]: https://www.selenium.dev/
[Puppeteer]: https://pptr.dev/
[Chrome WebDriver]: (https://developer.chrome.com/docs/chromedriver?hl=ja)
[jsDelivr]: https://www.jsdelivr.com/