QiitaのmarkdownでSVGを使って作図する

# はじめに

Qiita の markdown で簡単な図を描きたいと思っている人は多いのではないでしょうか。Qiita 公式からは mermaid や PlantUML などの作図言語が用意されているとはいえ，コレらはフローチャートのようなビジネスチャートを自動製図するのに特化している分，思ったような図が描けなくてモドカシイ思いをしている人もいるでしょう。

そんな人には朗報？かもしれません。Qiita でも直接 SVG（Scalable Vector Graphics）を使ってチマチマと作図できる方法を発見しました。とりあえず作図例を以下に示します。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="400" height="380" viewbox="0 0 400 380">
<circle cx="150" cy="225" r="100" fill="red"/>
<circle cx="250" cy="225" r="100" fill="blue"/>
<circle cx="200" cy="138.4" r="100" fill="lime"/>
<path d="M200 311.6 A100 100 0 0 0 250 225 A100 100 0 0 1 150 225 A100 100 0 0 0 200 311.6" fill="magenta"/>
<path d="M300 138.4 A100 100 0 0 0 200 138.4 A100 100 0 0 1 250 225 A100 100 0 0 0 300 138.4" fill="cyan"/>
<path d="M100 138.4 A100 100 0 0 1 200 138.4 A100 100 0 0 0 150 225 A100 100 0 0 1 100 138.4" fill="yellow"/>
<path d="M200 138.4 A100 100 0 0 1 250 225 A100 100 0 0 1 150 225 A100 100 0 0 1 200 138.4" fill="white"/>
<circle cx="150" cy="225" r="100" fill="none" stroke="black" stroke-width="2"/>
<circle cx="250" cy="225" r="100" fill="none" stroke="black" stroke-width="2"/>
<circle cx="200" cy="138.4" r="100" fill="none" stroke="black" stroke-width="2"/>
</svg>
title <$FIGURE>
caption 図１　ベン図を描きたい
@enduml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="400" height="360" viewbox="0 0 400 360">
<g fill="black">
<path d="M40 309 h330 v2 h-330"/>
<path d="M40 309 h360 v2 h-360" transform="rotate(-51.318,40,310)"/>
<path d="M370 309 h-300 v2 h300" transform="rotate(69.513,370,310)"/>
<path d="M219 310 v-284.6 h2 v284.6" transform="rotate(9.097,225,310)"/>
<path d="M40 339 h145 v2 h-145"/>
<path d="M370 339 h-155 v2 h155"/>
<path d="M39 330 v20 h2 v-20"/>
<path d="M369 330 v20 h2 v-20"/>
<path d="M42 339 h10 v2 h-10" transform="rotate(45,42,340)"/>
<path d="M42 339 h10 v2 h-10" transform="rotate(-45,42,340)"/>
<path d="M368 339 h-10 v2 h10" transform="rotate(45,368,340)"/>
<path d="M368 339 h-10 v2 h10" transform="rotate(-45,368,340)"/>
</g>
<text x="260" y="24" fill="black" font-size="16">A</text>
<text x="25" y="316" fill="black" font-size="16">B</text>
<text x="375" y="316" fill="black" font-size="16">C</text>
<text x="215" y="326" fill="black" font-size="16">D</text>
<circle cx="243" cy="75" r="4" fill="black" />
<circle cx="270" cy="75" r="4" fill="black" />
<text x="128" y="170" fill="black" font-size="16">12</text>
<text x="323" y="170" fill="black" font-size="16">10</text>
<text x="190" y="346" fill="black" font-size="16">11</text>
</svg>
title <$FIGURE>
caption 図２　三角形を描きたい
@enduml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="400" height="300" viewbox="0 0 400 300">
<g fill="black">
<path d="M24 74 h352 v2 h-352"/>
<path d="M24 124 h352 v2 h-352"/>
<path d="M24 174 h352 v2 h-352"/>
<path d="M24 224 h352 v2 h-352"/>
<path d="M24 274 h352 v2 h-352"/>
<path d="M24 24 v252 h2 v-252"/>
<path d="M75 24 v252 h2 v-252"/>
<path d="M124 24 v252 h2 v-252"/>
<path d="M174 24 v252 h2 v-252"/>
<path d="M224 24 v252 h2 v-252"/>
<path d="M274 24 v252 h2 v-252"/>
<path d="M324 24 v252 h2 v-252"/>
</g>
<text x="25" y="270" font-size="50" fill="black">香</text>
<text x="75" y="270" font-size="50" fill="black">桂</text>
<text x="125" y="270" font-size="50" fill="black">銀</text>
<text x="175" y="270" font-size="50" fill="black">金</text>
<text x="225" y="270" font-size="50" fill="black">玉</text>
<text x="275" y="270" font-size="50" fill="black">金</text>
<text x="75" y="220" font-size="50" fill="black">角</text>
<text x="25" y="120" font-size="50" fill="black">歩</text>
<text x="75" y="170" font-size="50" fill="black">歩</text>
<text x="125" y="120" font-size="50" fill="black">歩</text>
<text x="175" y="170" font-size="50" fill="black">歩</text>
<text x="225" y="120" font-size="50" fill="black">歩</text>
<text x="275" y="170" font-size="50" fill="black">歩</text>
</svg>
title <$FIGURE>
caption 図３　将棋の盤面図を描きたい
@enduml
```

# コレどうやって描いているの？

Qiita でもサポートされている PlantUML の隠し機能？であるインライン SVG スプライトを使います。もともとはスプライト（アイコン，絵文字，古くは外字とも）と呼ばれる小さな図形を SVG を使って定義し，それらのスプライトを再利用できるようにするといったものです。具体的には下記のように `FIGURE` という名のスプライトを SVG で定義した後で `<$FIGURE>` という形で参照します。

````markdown
```plantuml
@startuml
sprite FIGURE <svg width="500" height="500" viewbox="0 0 500 500">

（この中に SVG の図形要素を記述する）

</svg>
title <$FIGURE>
@enduml
```
````

ただし，残念ながら SVG の持つ豊富な機能のうち，ごくごく一部の機能しか実装していないようです。サポートしているタグは以下の５種類しかありません。厳密にいえば &lt;svg&gt; も加えて６種類ですが。

<table>
<caption>表１　PlantUML のインライン SVG スプライトのサポート機能一覧</caption>
<thead>
<tr><th style="text-align:center">タグ</th>
    <th style="text-align:center">機能</th>
    <th style="text-align:center">指定可能オプション</th></tr>
</thead>
<tbody>
<tr><td>&lt;g&gt;</td><td>グループ化</td><td>fill, transform</td></tr>
<tr><td>&lt;text&gt;</td><td>テキスト</td><td>x, y, fill, font-size</td></tr>
<tr><td>&lt;path&gt;</td><td>パス塗り潰し</td><td>d, fill, transform</td></tr>
<tr><td>&lt;circle&gt;</td><td>円描画・塗り潰し</td><td>cx, cy, r, fill, stroke, stroke-width, transform(?)</td></tr>
<tr><td>&lt;ellipse&gt;</td><td>楕円描画・塗り潰し</td><td>cx, cy, rx, ry, fill, stroke, stroke-width, transform</td></tr>
</tbody>
</table>

また，各タグの機能もサブセットというか，不完全な実装に留まっています。ここでは「できない」機能で気づいた点を挙げます。

- &lt;g&gt;
  グループ化した要素をまとめて回転させることができます。自分は塗り潰し色（fill オプション）をグループで定義して記述量を減らすのに用いています。なお，&lt;use&gt; タグが無いので定義したグループの再利用ができません。また &lt;text&gt; タグはグループ化の対象外のようです。
- &lt;text&gt;
  文字の回転ができません。テキストをグループ化して，グループ全体で回転させてもテキストだけ無効になってしまいます。なお全てのオプションの指定が必須であり，一つでも省略するとエラーになります。また描画位置の起点が左下端固定であり，中揃えや右揃え等の指定ができないため，中揃えに見せるためには文字幅を計算して位置を微調整する必要があります。
- &lt;path&gt;
  閉曲線の塗り潰しは可能ですが，境界線のみの描画はできません。
- &lt;circle&gt; と &lt;ellipse&gt;
  本来は塗り潰しと境界線の描画を同時に行うことができますが，このスプライト機能ではいずれか一方しかできません。具体的には fill="none" と指定しないと境界線が描かれません。このため境界線の色と塗り潰し色が異なる場合，タグを２回（先に塗り潰し，次に境界線の描くという形で）使うことになります。あと &lt;circle&gt; の transform（座標変換）はバグっているように感じます。とくに rotate の動作が奇妙に思います。

個人的には文字の回転ができない点が非常に残念であり，コレができれば将棋盤が作れたのにとは思います。また &lt;path&gt; で必ず塗り潰されてしまい，自由曲線を描けないことも惜しいところです。

:::note
機能限定については本記事執筆（2024年4月）時点の情報に基づきます。
:::

# 線の描き方

基本的に線（直線・曲線）を描くのが難しく，円や楕円の境界線は描けてもその一部だけ（円弧）は描けません。じゃあ，上の図ではどうしているのかというと，すごく細い矩形領域を塗り潰して作成し，回転させているだけです。まあ別に回転させずに平行四辺形を描いても良いのですが，線の太さが変わってしまいます。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="200" height="200" viewbox="0 0 200 200">
<path d="M25 146 h150 v8 h-150" fill="black"/>
</svg>
title <$FIGURE>
caption 回転前
@endutml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="200" height="200" viewbox="0 0 200 200">
<path d="M25 146 h150 v8 h-150" fill="black" transform="rotate(-30,25,150)"/>
</svg>
title <$FIGURE>
caption 回転後
@endutml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="200" height="200" viewbox="0 0 200 200">
<path d="M25 146 l129.9 -75 v8 l-129.9 75" fill="black"/>
</svg>
title <$FIGURE>
caption 平行四辺形
@endutml
```

```markdown:回転前
<path d="M25 146 h150 v8 h-150" fill="black"/>
```

```markdown:回転後
<path d="M25 146 h150 v8 h-150" fill="black" transform="rotate(-30,25,150)"/>
```

```markdown:平行四辺形
<path d="M25 146 l129.9 -75 v8 l-129.9 75" fill="black"/>
```

# 文字の表示位置

薄い黄色で示す正方形の領域（128×128ピクセル）の領域に対し，サイズ 128 ピクセルの英数文字を左下端を基準にして書き込んだ場合を示します。正方形の中心位置に揃えようとすると概ね右方向に 20 ピクセル，上方向に 16 ピクセル移動させる必要があります。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="192" height="192" viewbox="0 0 192 192">
<path d="M32 32 h128 v128 h-128" fill="lightyellow"/>
<path d="M32 64 h128 v1 h-128" fill="red"/>
<path d="M120 32 v128 h1 v-128" fill="red"/>
<text x="32" y="160" fill="black" font-size="128">A</text>
<text x="66" y="32" fill="red" font-size="16">88</text>
<text x="130" y="32" fill="red" font-size="16">40</text>
<text x="160" y="54" fill="red" font-size="16">32</text>
<text x="160" y="118" fill="red" font-size="16">96</text>
</svg>
title <$FIGURE>
caption 英数文字（調整前）
@endutml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="192" height="192" viewbox="0 0 192 192">
<path d="M32 32 h128 v128 h-128" fill="lightyellow"/>
<path d="M32 48 h128 v1 h-128" fill="red"/>
<path d="M32 144 h128 v1 h-128" fill="red"/>
<path d="M52 32 v128 h1 v-128" fill="red"/>
<path d="M140 32 v128 h1 v-128" fill="red"/>
<text x="52" y="144" fill="black" font-size="128">A</text>
<text x="32" y="32" fill="red" font-size="16">20</text>
<text x="86" y="32" fill="red" font-size="16">88</text>
<text x="140" y="32" fill="red" font-size="16">20</text>
<text x="160" y="48" fill="red" font-size="16">16</text>
<text x="160" y="102" fill="red" font-size="16">96</text>
<text x="160" y="160" fill="red" font-size="16">16</text>
</svg>
title <$FIGURE>
caption 英数文字（調整後）
@endutml
```

一方，漢字などのワイド文字の場合，横幅はちょうどよいのですが，何故か上下方向はベースラインを突き抜けています。これも上方向に 16 ピクセル移動させると正方形の中心位置に揃います。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="192" height="192" viewbox="0 0 192 192">
<path d="M32 32 h128 v128 h-128" fill="lightyellow"/>
<text x="32" y="160" fill="black" font-size="128">飛</text>
</svg>
title <$FIGURE>
caption 漢字（調整前）
@endutml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
caption {
  fontSize 16
}
</style>
sprite FIGURE <svg width="192" height="192" viewbox="0 0 192 192">
<path d="M32 32 h128 v128 h-128" fill="lightyellow"/>
<text x="32" y="144" fill="black" font-size="128">飛</text>
</svg>
title <$FIGURE>
caption 漢字（調整後）
@endutml
```

文字の違い（英数文字，漢字）に注意すれば，フォントサイズに比例してこれらの位置補正を行えば良いようです。正確を期す人はフォントのメトリクス情報を確認したほうが良いでしょう。

# 拡大縮小機能

定義したスプライトを配置するときに拡大・縮小できます。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
</style>
sprite FIGURE <svg width="64" height="64" viewbox="0 0 64 64">
<path d="M0 64 h64 l-12 -48 l-20 -16 l-20 16" fill="yellow"/>
</svg>
title <$FIGURE*2> <$FIGURE> <$FIGURE*0.5>
@endutml
```

`<$FIGURE*2>` のように倍率を書けます。倍率は `<$FIGURE*0.5>` のように小数でも大丈夫です。

````markdown
```plantuml
@startuml
sprite FIGURE <svg width="128" height="128" viewbox="0 0 128 128">
<path d="M0 64 h64 l-12 -48 l-20 -16 l-20 16" fill="yellow"/>
</svg>
title <$FIGURE*2> <$FIGURE> <$FIGURE*0.5>
@endutml
```
````

ただし，テキストだけ拡大・縮小できないのが残念なところです。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
</style>
sprite FIGURE <svg width="64" height="64" viewbox="0 0 64 64">
<path d="M0 64 h64 l-12 -48 l-20 -16 l-20 16" fill="yellow"/>
<text x="12" y="56" fill="black" font-size="40">歩</text>
</svg>
title <$FIGURE*2> <$FIGURE> <$FIGURE*0.5>
@endutml
```

````markdown
```plantuml
@startuml
sprite FIGURE <svg width="64" height="64" viewbox="0 0 64 64">
<path d="M0 64 h64 l-12 -48 l-20 -16 l-20 16" fill="yellow"/>
<text x="12" y="56" fill="black" font-size="40">歩</text>
</svg>
title <$FIGURE*2> <$FIGURE> <$FIGURE*0.5>
@endutml
```
````