パラグラフの改行を CSS から対応したい

<!-- # パラグラフの改行を CSS から対応したい -->

Markdown で文章を書いていると、句点 (`。`) の後ろで 1 回だけ改行している状況があります。このとき、執筆者はこの改行がそのまま改行として解釈されて欲しいと感じていることでしょう。

しかし、改行を強制改行として解釈しない Markdown パーサでは、改行が半角スペースとなってしまいます。

本記事では、このような改行が改行として解釈されない原因と CSS による対応方法を紹介します。

## Markdown における改行の処理

Markdown の仕様の 1 つである CommonMark では、パラグラフ内の改行は “[Soft line breaks][slb]” と呼ばれる仕様に記載されています。
この Soft line breaks は、ソース内の改行や空白文字に関して以下のように規定します。

[slb]: https://spec.commonmark.org/0.31.2/#soft-line-breaks "Soft line breaks - CommonMark Spec"

- ソース内の改行はそのままにする（[例 648][ex648]）
- 行頭・行末の空白文字を無視する [^head-tail]（[例 649][ex649]）

[ex648]: https://spec.commonmark.org/0.31.2/#example-648 "Example 648 - CommonMark Spec"
[ex649]: https://spec.commonmark.org/0.31.2/#example-649 "Example 649 - CommonMark Spec"

[^head-tail]: Markdown では、行頭と行末の半角スペースは特別な意味を持つことがあります。

    - 行頭に 4 つ以上の半角スペース：<br>Indented code block として解釈される
    - 行末に 2 つ分以上の半角スペース：<br>強制改行に解釈される

そのため、Markdown から HTML に変換されても、ソース内の改行がそのまま `<p>` タグに入ってきます。（改行を分かりやすくするために、改行を `↵` としています）

```markdown
吾輩は猫である。↵
名前はまだない。
```

⇓

```html
<p>吾輩は猫である。↵
名前はまだない。</p>
```

では、HTML 内にある改行はどうして反映されないのでしょうか。

これは **CSS の影響を受けるため** です。

----------

ちなみに、ソース内の改行が強制改行に解釈されるパーサ（例えば Qiita）では、改行位置で `<br>` が挿入されるようになります。
すなわち、以下の HTML のように解釈されています。

```html
<p>吾輩は猫である。<br>
名前はまだない。</p>
```

この場合、Markdown ソース内の改行は `<br>` となるため、CSS に関わらず強制的に改行となります。

## プロパティによる制御

[`white-space`][white-space] は文章の折り返しや改行や空白文字等を制御するためのプロパティです。このプロパティを変更することでソース内の改行を改行として扱わせることが出来ます。

この [`white-space`][white-space] は以下 6 つの値を採ります。
（`white-space` は行末の空白文字に関する挙動も制御しますが、Markdown では行末の空白文字を強制改行または無視するため、ここでは省略しています）

[white-space]: https://developer.mozilla.org/ja/docs/Web/CSS/white-space "white-space - CSS: カスケーディングスタイルシート | MDN"

|                        | 折り返し | 連続する<br>空白文字 |   改行   |
| :--------------------: | :------: | :------------------: | :------: |
| `normal`<br> (default) |    有    |       まとめる       | まとめる |
|       `pre-line`       |    有    |       まとめる       | そのまま |
|       `pre-wrap`       |    有    |       そのまま       | そのまま |
|     `break-spaces`     |    有    |       そのまま       | そのまま |
|        `nowrap`        |    無    |       まとめる       | まとめる |
|         `pre`          |    無    |       そのまま       | そのまま |

※ 改行は “まとめる” となっていますが、まとめた上で半角スペースに置き替えられています。

これを踏まえると、改行が半角スペースとして置き替えられていた原因は、デフォルトの `white-space: normal` となっていたからでした。

このプロパティを使って、`<p>` タグ内の改行をそのまま出力するように構成すれば目標達成です。
今回、求めている `white-space` の状態は、以下のような状態です。

- 折り返し ⇒ 有
- 連続する空白文字 ⇒ まとめる
- 改行 ⇒ そのまま

そのため、`pre-line` がふさわしいでしょう。

もちろん、この処理は意図的に改行を改行として機能させるため、マンページのような特定の文字数で改行するような文章には不向きな構成になっていることに注意してください。

### 具体的な CSS の構成

`<p>` タグの `white-space` を `pre-line` にすれば良いので、以下のように構成します。

```css
p {
  white-space: pre-line;
}
```

ここでは `<p>` タグ内のみに影響します。そのため、`<li>` や `<blockquote>` タグ内には影響しない可能性があります。影響させたい場合は、適宜 CSS を編集する必要があります。

## 複数改行したいとき

Markdown ファイル内で複数回にわたる改行をした場合、どのような結果になるでしょうか。

例えば、以下のような状況です。

```markdown
吾輩は猫である。↵
↵
↵
名前はまだない。
```

このとき、`white-space: pre-line` が指定されていた場合、改行はそのままになるはずだと考えるかもしれません。
しかし、実際このような複数回の改行は無視されます。

これは **Markdown の処理による影響** です。

[CommonMark][paragraphs] に依れば、改行が 2 回以上続くような場合、パラグラフブロックとしての意味が途切れます。
すなわち、上の例では 1 文目と 2 文目は異なるパラグラフ (`<p>`) として処理されるため、HTML では次のように処理されています。

[paragraphs]: https://spec.commonmark.org/0.31.2/#paragraphs "Paragraphs - CommonMark Spec"

```html
<p>吾輩は猫である。</p>
<p>名前はまだない。</p>
```

そのため、異なる `<p>` タグ内に含まれている以上、どれだけ改行をしても改行が陽に出てくることはありません。[^linebreaks]

[^linebreaks]: 仮に `white-space: pre-line` 下で HTML を用いて次のように書いたとしても、改行は上手く処理されません。

    ```markdown
    <p>吾輩は猫である。↵
    ↵
    ↵
    名前はまだない。</p>
    ```

    Markdown は、この複数行にわたる改行のために 1 文目と 2 文目のブロックを異なるブロックとして扱います。（1 文目は HTML ブロック、2 文目はパラグラフです）

    そのため、 以下の HTML のように解釈されてしまいます。

    ```markdown
    <p>吾輩は猫である。
    <p>名前はまだない。</p></p>
    ```

    このような結果は不正であり、HTML で複数回の改行をするべきではありません。

もしも、Markdown 内で改行を陽に出したい場合は、強制改行を利用する必要があります。

### 強制改行

強制改行を行うには、Markdown による方法と HTML による方法の 2 つがあります。

- 行末に `\` を置く (Markdown)
    ```markdown
    吾輩は猫である。\↵
    名前はまだない。
    ```
- 行末に連続した 2 つの半角スペースを置く (Markdown)
    ```markdown
    吾輩は猫である。  ↵
    名前はまだない。
    ```
- 文章の途中で `<br>` を置く (HTML)
    ```markdown
    吾輩は猫である。<br>名前はまだない。
    ```

いずれの方法であっても同じ結果を得ます。（もちろん、HTML 内に改行が残る残らないの違いはあります）

## 余談

Qiita や Zenn といったサービスの Markdown は改行に強制改行が挿入される仕様なので、CSS による方法を取る必要はありません（そもそも CSS はいじれない）が、はてなブログのような改行が強制改行にならないが CSS はいじれるようなサービス [^hatena] であれば、非常に有用なテクニックになると思います。

[^hatena]: はてなブログでは、“デザイン設定” ⇒ “カスタマイズ 🔧” ⇒ 高度な設定の “デザイン CSS” で CSS を記述すれば完了です。

### 改行をそのまま無くすには

日本語を含む CJK 圏での文章では分かち書きをしません。そのため、1 つの文を複数行に改行する場合（マンページのような特定の文字数で改行する場合）には、単に改行を取り除くことが求められます。

しかし、現在の HTML ではソース内の改行を単に取り除くことは出来ません。
先に示した `white-space` プロパティによって、ソース内の改行がまとめられて半角スペースになるかそのままになるかの 2 通りです。

例えば、`white-space: normal` の場合、次のような文章の改行毎に半角スペースが挿入されます。

```html
<p>隴西の李徴は博学才穎、天宝の末↵
年、若くして名を虎榜に連ね、つ↵
いで江南尉に補せられたが、性、↵
狷介、自ずから恃むところ頗る厚↵
く、賤吏に甘んずるを潔しとしな↵
かった。</p>
```

そのため、日本語を含む CJK 圏で文章の途中で改行することは現実的ではありませんでした。

この問題に対して、改行の前後のコンテキストに応じて（例えば、CJK の場合）改行を単に取り除くとする案が [W3C ドラフト][w3c] として提起されています。[^draft]

[^draft]: ドラフト段階ですが、ブラウザによっては取りこまれているようです。（[参照][yasuraok]）

[w3c]: https://www.w3.org/TR/css-text-4/#line-break-transform "CSS Text Module Level 4 - W3C Working Draft, 19 February 2024"
[yasuraok]: https://zenn.dev/yasuraok/articles/7c8a6dc3f9bb10#%E4%BB%95%E6%A7%98%E3%81%A8%E5%AE%9F%E9%9A%9B%E3%81%AE%E6%8C%99%E5%8B%95%E3%81%AE%E6%A4%9C%E8%A8%BC "§ 仕様と実際の挙動の検証 - htmlやmarkdownで日本語長文を書く時に改行すると半角スペースが残る問題"

### 行頭インデント

本記事の趣旨とは若干離れますが、Markdown パラグラフの問題の 1 つにパラグラフの行頭インデントが無いことが挙げられます。

Markdown が CommonMark の Soft line breaks 仕様に従う限り、行頭の空白文字は無視されます。そのため、行頭インデントを全角スペース等で行うことは出来ません。

しかし、CSS を調整できるのであれば、[`text-indent`][text-indent] から行頭インデントを指定できます。

[text-indent]: https://developer.mozilla.org/ja/docs/Web/CSS/text-indent "text-indent - CSS: カスケーディングスタイルシート | MDN"

```css
p {
  text-indent: 1em;
}
```

ちなみに、力技として [`&#12288;`][u+3000] を行頭に置くことで疑似的なインデントを課すことも出来ますが、あまりお薦めしません。変なので。

[u+3000]: https://www.compart.com/en/unicode/U+3000 "“　” U+3000 Ideographic Space Unicode Character"

ちなみに、`white-space: pre-line` による改行後の行頭にはインデントが課されません。（`<p>` の頭にインデントが課されるだけなので）[^linespace]

[^linespace]: CSS でパラグラフどうしの間隔を狭めれば、`white-space: pre-line` にこだわらずに各パラグラフに行頭インデントを課せます。（HTML 的にはこれが最適解？）