Qiitaのmarkdownでコードブロックをピンクに染める

下記の記事において，筆者がコメントで使ったテクニックの種明かしをします。

https://qiita.com/moririn086/items/591c9de70368dc4c5333#comment-d86839e3715646945704

記事本文であれば「**Markdown** で本文を見る」という機能を使えば **Markdown** を読めるので，その記事の著者がどんなテクニックを使っているのかリバースエンジニアリングして勉強できるのですが，残念ながら現時点の **Qiita** の機能ではコメントの **Markdown** を見ることができないからです。

# コードスパンをピンクに染める

まず筆者のコメントで使用しているコードスパン（風）のトリックを明かします。

> どうせなら本文に貼り付ける $\bbox[#FFE4E1,2px]{\color{#C71585}{\small\texttt{settings.json}}}$ もピンクに染めたい。

ここは **MathJax** を使っています。

```
$\bbox[#FFE4E1,2px]{\color{#C71585}{\small\texttt{settings.json}}}$
```

ここで背景色 `#FFE4E1` は `\bbox`，文字色 `#C71585` は `\color` を用いて指定しています。なお **MathJax** でも書体を選択できますが，残念ながら本物のコードスパンと完全一致するものはありません。なので最も近いと思われる **typewriter typestyle** を選びました。
※この辺りは **Web** ブラウザにも依存するので難しいところです。

<table>
<thead>
<tr><th style="text-align:center">書体</th>
    <th style="text-align:center">実際の字体</th>
    <th style="text-align:center">MathJax 指定</th></tr>
</thead>
<tbody>
<tr></tr>
<tr><th>roman</th>
    <td>$\bbox[#f0f0f0,2px]{\small\textrm{settings.json}}$</td>
    <td><code>\textrm</code></td></tr>
<tr></tr>
<tr><th>bold font</th>
    <td>$\bbox[#f0f0f0,2px]{\small\textbf{settings.json}}$</td>
    <td><code>\textbf</code></td></tr>
<tr></tr>
<tr><th>italic</th>
    <td>$\bbox[#f0f0f0,2px]{\small\textit{settings.json}}$</td>
    <td><code>\textit</code></td></tr>
<tr></tr>
<tr><th>sans serif</th>
    <td>$\bbox[#f0f0f0,2px]{\small\textsf{settings.json}}$</td>
    <td><code>\textsf</code></td></tr>
<tr></tr>
<tr><th>typewriter typestyle</th>
    <td>$\bbox[#f0f0f0,2px]{\small\texttt{settings.json}}$</td>
    <td><code>\texttt</code></td></tr>
<tr></tr>
<tr><th>本物のコードスパン</th>
    <td><code>settings.json</code></td>
    <td></td></tr>
</tbody>
</table>

なお，**MathJax** の詳しい記法は参考文献[^1]を参考にさせて頂きました。

# コードブロックをピンクに染める

### コードブロックの全容

次はコードブロック（風）のトリックを明かします。まずは全容を示しましょう。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 0 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #000000
}
</style>
sprite TITLE <svg width="320" height="24">
<path d="M0 0 v18 a6 6 0 0 0 6 6 h92 a6 6 0 0 0 6 -6 v-18" fill="#FFC0CB" />
<text x="7" y="16" font-size="14" fill="#C71585">settings.json</text>
</svg>
title
<$TITLE>

{
  <color:#C71585>"workbench.colorTheme"</color>: <color:#FF6347>"Default Light Modern"</color>,
  <color:#C71585>"editor.fontFamily"</color>:
  <color:#FF6347>"'Rounded M+', 'Noto Sans JP', 'メイリオ', 'ＭＳ ゴシック', monospace"</color>,
  <color:#C71585>"workbench.colorCustomizations"</color>: {
    <color:#A020F0>// エディタの背景とテキストの色</color>
    <color:#C71585>"editor.background"</color>: <color:#FF6347>"#FFE4E1"</color>,                  <color:#A020F0>// ミスティローズ</color>
    <color:#C71585>"editor.foreground"</color>: <color:#FF6347>"#000000"</color>,                  <color:#A020F0>// 黒色</color>

    <color:#A020F0>// 選択範囲と現在の行のハイライト</color>
    <color:#C71585>"editor.selectionBackground"</color>: <color:#FF6347>"#FFB6C1"</color>,         <color:#A020F0>// ライトピンク</color>
    <color:#C71585>"editor.lineHighlightBackground"</color>: <color:#FF6347>"#FFD1DC"</color>,     <color:#A020F0>// ピンク</color>

    <color:#A020F0>// カーソルの色</color>
    <color:#C71585>"editorCursor.foreground"</color>: <color:#FF6347>"#FF69B4"</color>,            <color:#A020F0>// ホットピンク</color>

    <color:#A020F0>// 行番号</color>
    <color:#C71585>"editorLineNumber.foreground"</color>: <color:#FF6347>"#DB7093"</color>,        <color:#A020F0>// パレバイオレットレッド</color>
    <color:#C71585>"editorLineNumber.activeForeground"</color>: <color:#FF6347>"#C71585"</color>,  <color:#A020F0>// ミディアムバイオレットレッド</color>

    <color:#A020F0>// 括弧のマッチング</color>
    <color:#C71585>"editorBracketMatch.background"</color>: <color:#FF6347>"#FFC0CB"</color>,      <color:#A020F0>// ピンク</color>
    <color:#C71585>"editorBracketMatch.border"</color>: <color:#FF6347>"#FF69B4"</color>,          <color:#A020F0>// ホットピンク</color>

    <color:#A020F0>// エラーと警告</color>
    <color:#C71585>"editorError.foreground"</color>: <color:#FF6347>"#FF4500"</color>,             <color:#A020F0>// オレンジレッド</color>
    <color:#C71585>"editorWarning.foreground"</color>: <color:#FF6347>"#FFA07A"</color>,           <color:#A020F0>// ライトサーモン</color>

    <color:#A020F0>// ステータスバー</color>
    <color:#C71585>"statusBar.background"</color>: <color:#FF6347>"#FFB6C1"</color>,               <color:#A020F0>// ライトピンク</color>
    <color:#C71585>"statusBar.foreground"</color>: <color:#FF6347>"#000000"</color>,               <color:#A020F0>// 黒色</color>

    <color:#A020F0>// サイドバー</color>
    <color:#C71585>"sideBar.background"</color>: <color:#FF6347>"#FFF0F5"</color>,                 <color:#A020F0>// ラベンダーブラッシュ</color>
    <color:#C71585>"sideBar.foreground"</color>: <color:#FF6347>"#000000"</color>,                 <color:#A020F0>// 黒色</color>

    <color:#A020F0>// アクティビティバー</color>
    <color:#C71585>"activityBar.background"</color>: <color:#FF6347>"#FFC0CB"</color>,             <color:#A020F0>// ピンク</color>
    <color:#C71585>"activityBar.foreground"</color>: <color:#FF6347>"#000000"</color>,             <color:#A020F0>// 黒色</color>
    <color:#C71585>"activityBarBadge.background"</color>: <color:#FF6347>"#FF69B4"</color>,        <color:#A020F0>// ホットピンク</color>
    <color:#C71585>"activityBarBadge.foreground"</color>: <color:#FF6347>"#FFFFFF"</color>,        <color:#A020F0>// 白色</color>

    <color:#A020F0>// タブ</color>
    <color:#C71585>"tab.activeBackground"</color>: <color:#FF6347>"#FFB6C1"</color>,               <color:#A020F0>// ライトピンク</color>
    <color:#C71585>"tab.inactiveBackground"</color>: <color:#FF6347>"#FFE4E1"</color>,             <color:#A020F0>// ミスティローズ</color>
    <color:#C71585>"tab.activeForeground"</color>: <color:#FF6347>"#000000"</color>,               <color:#A020F0>// 黒色</color>
    <color:#C71585>"tab.inactiveForeground"</color>: <color:#FF6347>"#696969"</color>,             <color:#A020F0>// ディムグレー</color>

    <color:#A020F0>// スクロールバー</color>
    <color:#C71585>"scrollbarSlider.activeBackground"</color>: <color:#FF6347>"#FF69B4AA"</color>, <color:#A020F0>// 半透明ホットピンク</color>
    <color:#C71585>"scrollbarSlider.background"</color>: <color:#FF6347>"#FFB6C1AA"</color>,       <color:#A020F0>// 半透明ライトピンク</color>
    <color:#C71585>"scrollbarSlider.hoverBackground"</color>: <color:#FF6347>"#FF69B4AA"</color>   <color:#A020F0>// 半透明ホットピンク</color>
  },
  <color:#C71585>"editor.tokenColorCustomizations"</color>: {
    <color:#C71585>"textMateRules"</color>: [
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"comment"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#A020F0"</color> <color:#A020F0>// パープル</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"string"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#A020F0"</color> <color:#A020F0>// ディープピンク</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"keyword"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#FF69B4"</color> <color:#A020F0>// ホットピンク</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"entity.name.function"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#DB7093"</color> <color:#A020F0>// パレバイオレットレッド</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"variable"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#C71585"</color> <color:#A020F0>// ミディアムバイオレットレッド</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"constant.numeric"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#FF4500"</color> <color:#A020F0>// オレンジレッド</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"storage.type"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#FF6347"</color> <color:#A020F0>// トマト</color>
        }
      },
      {
        <color:#C71585>"scope"</color>: <color:#FF6347>"keyword.operator"</color>,
        <color:#C71585>"settings"</color>: {
          <color:#C71585>"foreground"</color>: <color:#FF6347>"#FF69B4"</color> <color:#A020F0>// ホットピンク</color>
        }
      }
    ]
  }
}
end title
@enduml
```

ここは **PlantUML** を用いています。これから順に説明しますが，気になる方はここで「**Markdown** で本文を見る」機能を使って **Markdown** を直接見てください。

### スタイル設定

まずは冒頭のオマジナイです。コードブロックはすべて図のタイトル部分に記載しており，ここではそのタイトル部分のスタイルを設定しています。背景色のミスティローズ `#FFE4E1` はここで設定しています。

````
```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 0 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #000000
}
</style>
````

### インライン SVG スプライト

次がファイル名である `settings.json` の表示部分です。**PlantUML** の持つインライン **SVG** スプライトという技術を用いています。インライン **SVG** スプライトについては筆者の過去記事[^2]を参照下さい。

````
sprite TITLE <svg width="320" height="24">
<path d="M0 0 v18 a6 6 0 0 0 6 6 h92 a6 6 0 0 0 6 -6 v-18" fill="#FFC0CB" />
<text x="7" y="16" font-size="14" fill="#C71585">settings.json</text>
</svg>
````

二倍に拡大したものを以下に示します。角の丸めも再現していることが分かります。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 32
  padding 0 32 32 32
  fontStyle normal
  fontSize 28
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #000000
}
</style>
sprite TITLE <svg width="640" height="24">
<path d="M0 0 v36 a12 12 0 0 0 12 12 h184 a12 12 0 0 0 12 -12 v-36" fill="#FFC0CB" />
<text x="14" y="32" font-size="28" fill="#C71585">settings.json</text>
</svg>
title
<$TITLE>

end title
@enduml
```

なお，このインライン **SVG** スプライトはコードブロックの横幅の最小値を確保するためにも利用しています。`<svg width="320" height="24">` と記述すると **320px** となります。

### コード本体

次がコード本体です。コード本体は `title` ～ `end title` の中に記述します。まず先頭でインライン **SVG** スプライトを配置し，その後一行空けてコード本体を記載しています。この際に `<color>` タグで色付けを行っています。

````
title
<$TITLE>

{
  <color:#C71585>"workbench.colorTheme"</color>: <color:#FF6347>"Default Light Modern"</color>,
  <color:#C71585>"editor.fontFamily"</color>:
  <color:#FF6347>"'Rounded M+', 'Noto Sans JP', 'メイリオ', 'ＭＳ ゴシック', monospace"</color>,

  ～中略～

}
end title
@enduml
```
````

これらのタグ付けを自動的に行うために下記の **Ruby** スクリプトを作成しました。**Qiita** ではコードブロックのシンタクスハイライトに **Ruby** の **Rouge** を使っている[^3]ので同じ技術を使いました。**Ruby** および **Rouge** のインストールについては筆者の過去記事[^4]を参照下さい。

```rb:json5.rb
require "rouge"
if ARGV.size == 0 then
  puts "usage: json5(.rb) [filename(.json)]"
  exit
end
filename = ARGV[0]
if !File.exist?(filename) then
  puts "#{filename} is not found."
  exit
end
input = File.open(filename, "rt:BOM|utf-8")
source = input.read
lexer = Rouge::Lexers::JSON5.new
html = Rouge::Formatters::HTML.new
text = html.format(lexer.lex(source))
text.gsub!(/<span class="p">(.+?)<\/span>/){$1}
text.gsub!(/<span class="w">(\s+)<\/span>/){$1}
text.gsub!(/<span class="c">/, "<color:#A020F0>")
text.gsub!(/<span class="nl">/, "<color:#C71585>")
text.gsub!(/<span class="s2">/, "<color:#FF6347>")
text.gsub!(/<\/span>/, "</color>"}
puts text
exit
```

`html.format()` は **HTML** 形式で出力するので，**PlantUML** の記法に置換します。なお，下記のように色付けを設定しました。クラスの詳細については本家サイト[^5]を参照ください。

<table>
<thead>
<tr><th>クラス</th><th>意味</th><th>変換色</th></tr>
</thead>
<tbody>
<tr></tr>
<tr><th>p</th><td>区切り記号</td><td>地色</td>
<tr></tr>
<tr><th>w</th><td>空白</td><td>地色</td>
<tr></tr>
<tr><th>c</th><td>コメント</td><td><code>#A020F0</code> パープル</td>
<tr></tr>
<tr><th>nl</th><td>ラベル名</td><td><code>#C71585</code> ミディアムバイオレットレッド</td>
<tr></tr>
<tr><th>s2</th><td>文字列（二重引用符付き）</td><td><code>#FF6347</code> トマト</td>
</tbody>
</table>


こうして下記のように **Ruby** スクリプトを実行して，生成された `settings.txt` を `title` ～ `end title` の間に貼り付ければ出来上がりです。

```:Ruby スクリプトの実行
c:\Qiita>json5 settings.json > settings.txt
```

※変換元の `settings.json` の文字コードは **UTF-8** 形式にしておいて下さい。

# 注意事項

本技術を汎用的に使おうとすると **PlantUML** のマークアップ記法（**Creole**）に注意する必要があります。例えば下記の **C** ソースコードを例にとります。

```
#include <stdio.h>
#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
```

上記のコードを **PlantUML** の `title` ～ `end title` 内にそのまま貼り付けると下記のようになります。行頭の記号 `#` は順序付きリスト（**Ordered List**）の記号として取り扱われてしまうのです。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 16 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #C71585
}
</style>
title
#include <stdio.h>
#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
end title
@enduml
```

これを防ぐためには記号 `#` の前にエスケープ記号のチルダ `~` を付けます。

```
~#include <stdio.h>
~#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
```

こうすると **PlantUml** でも正しく表記できるようになります。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 16 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #C71585
}
</style>
title
~#include <stdio.h>
~#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
end title
@enduml
```

しかし，次のように記号を連続させた場合が厄介です。

```
 1 -
 2 --
 3 ---
 4 ----
 5 -----
 6 ------
 7 -------
 8 --------
 9 ---------
10 ----------
```

**Creole** では文字列の左右を連続したハイフン `--` で囲むと取り消し表現になるので，ハイフンが５文字連続すると等号 `=`（あるいは太いハイフン `━`）に見えますが，実はハイフンの上に取り消し記号が重なった形なのです。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 16 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #C71585
}
</style>
title
 1 -
 2 --
 3 ---
 4 ----
 5 -----
 6 ------
 7 -------
 8 --------
 9 ---------
10 ----------
end title
@enduml
```

今のところ，これらを簡単にエスケープさせる方式が思いつきません。ということで本技術を汎用的に用いるには，まだまだ課題が残っているのですが，たまたま今回の事例では問題なかったので使ってみました。

# おまけ

文字色が単色で良いなら，**PlantUML** のコードブロック `<code>` ～ `</code>` を使うという方法があります。コードブロック内では **Creole** 記法を気にすることなく自由に記述できます。逆に言うと色を変更することができませんが。

`title` ～ `end title` の中には下記のように記述します。

```
<code>
//------------------------------
// Hello, World
//------------------------------
#include <stdio.h>
#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
</code>
```

こうすると `<code>` ～ `</code>` の内容がそのまま出力されます。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  horizontalAlignment left
  roundCorner 16
  padding 16 16 16 16
  fontStyle normal
  fontSize 14
  fontName monospaced
  backgroundColor #FFE4E1
  fontColor #C71585
}
</style>
title
<code>
//------------------------------
// Hello, World
//------------------------------
#include <stdio.h>
#include <stdlib.h>
int main() {
  printf("Hello, World\n");
  return 0;
}
</code>
end title
@enduml
```

# 最後に一言

**PlantUML** によるコードブロック（風）はシンタクスハイライトを付けて綺麗に見せてはいますが，残念ながらテキストをコピーできません。単なるビットマップ画像を張り付けたのと変わらず，読者にとってはコードの再利用性が低くなっています。用法用量にはご注意ください。

<!--
# 参考文献
-->

[^1]: [MathJax で利用可能な TeX コマンド（非公式）](https://memopad.bitter.jp/web/mathjax/TeXSyntax.html)

[^2]: [QiitaのmarkdownでSVGを使って作図する - Qiita](https://qiita.com/TETSURO1999/items/870962714724dde98651)

[^3]: [Qiitaでシンタックスハイライト可能な言語一覧 - Qiita](https://qiita.com/Qiita/items/e84f5aad7757afce82ba)

[^4]: [Qiitaでバッチファイルの謎なシンタクスハイライトを直したい - Qiita](https://qiita.com/TETSURO1999/items/3378c3ad7f2acb111f26)

[^5]: [List of tokens - rouge-ruby/rouge](https://github.com/rouge-ruby/rouge/wiki/List-of-tokens)