概要
Markdownで **太字** と書いたのに太字にならない、という現象がある。日本語の文章で句読点のすぐ隣に太字を置いたときに起きる。CommonMarkの仕様に起因する問題で、remark-cjk-friendly というプラグインで解決できる。そのまとめ。
問題の具体例
こういうMarkdownを書いたとする。
これは**重要な点。**続きのテキスト
テスト**「該当箇所」**を確認する
レンダリング結果では太字にならず、** がそのまま表示されてしまう。句読点や括弧がボールドの区切り文字 ** のすぐ内側にあり、かつその外側にCJK文字が続いている場合に起きる。
英語では単語間にスペースがあるので、この問題が起きにくい。This is **important.** Next sentence でも太字になる。日本語はスペースなしで文字が続くため、条件に引っかかりやすい。
CommonMarkの仕様: flanking delimiter run
CommonMarkという仕様がある。Markdownのパース方法を定義した仕様で、GitHubやremark(Astroが内部で使うMarkdownパーサー)はこれに準拠している。
CommonMark仕様のSection 6.2に「flanking delimiter run(区切り文字ランの隣接ルール)」というルールがある。** が太字の開始として認識されるには「left-flanking delimiter run」でなければならない、というルールだ。
left-flanking delimiter runの条件はこう。
- 直後が空白文字ではない
- かつ、直後が句読点文字(Unicode一般カテゴリ P・S)の場合は、直前が空白文字または句読点文字であること
right-flanking delimiter runも同様の条件がある。
なぜ日本語で問題が起きるか
。(U+3002、句点)や 「(U+300C)、」(U+300D)などはUnicodeの一般カテゴリが「P(Punctuation、句読点)」に分類されている。
これは**重要な点。**続きのテキスト を解析するとき、閉じの ** の直前に 。(句読点)がある。right-flanking delimiter runの条件として、直前が句読点の場合は直後が空白か句読点でなければならない。しかし ** の直後は 続(CJK文字)で、空白でも句読点でもないため、条件を満たせない。
結果として閉じの ** がright-flanking delimiter runとして認識されず、太字が閉じない。
同様に、開きの ** の直後に句読点がある場合も、left-flanking delimiter runの条件を満たせなくなる。
英語では単語間にスペースがあるので、** の外側にスペースが入り、flanking条件を満たしやすい。日本語はスペースなしで文字が続くため、句読点が ** の内側にあると条件に引っかかる。
CommonMarkのIssue
この問題はCommonMarkのIssueトラッカーでも議論されている。
CJK文字のような空白なし言語では、仕様の句読点ルールが意図しない動作を引き起こすという問題提起で、2017年から続いているIssue。仕様レベルでの対応はまだ行われていない。
影響を受ける文字の例
句読点カテゴリ(P・S)に該当するCJK文字で、よく文の前後に現れるもの:
。(句点、U+3002)、(読点、U+3001)」(右隅付き括弧、U+300D)』(右白隅付き括弧、U+300F))(全角右括弧、U+FF09)!(全角感嘆符、U+FF01)?(全角疑問符、U+FF1F):(全角コロン、U+FF1A)
remark-cjk-friendly
remark-cjk-friendly というプラグインがある。remarkのプラグインで、CJK文字が隣接した場合でも ** が太字として認識されるようにパースを修正する。
インストール:
pnpm add remark-cjk-friendly
Astroの設定:
// astro.config.ts
import { defineConfig } from "astro/config";
import remarkCjkFriendly from "remark-cjk-friendly";
export default defineConfig({
markdown: {
remarkPlugins: [remarkCjkFriendly],
},
});
これだけで、日本語の句読点に隣接した **text** が正しく太字としてレンダリングされるようになる。
余談
Markdownの太字の記法自体は何も変える必要がない。プラグインを追加するだけで既存の記事も含めて修正される。
ただ一点注意がある。このプラグインを入れると、今まで ** がそのまま表示されていた箇所が突然太字になる。意図せず ** を書いていた場所があれば表示が変わるので、適用後に確認するのが無難。