何番煎じ感はありますが、markdownを入力としていい感じの組版をしたいと思いました。
実現したこと
- markdownをPDFに出力する
- ディレクトリ横断して1ファイルに出力する
- 任意のCSSを適用する
- コードブロックに任意のキャプションを表示する(Qiitaで ```python:test.pyって書くようなやつ)
- 任意の箇所で改ページする
markdownをPDFに出力する
PDFの出力にはpandocを利用しました
pandocのインストール
筆者の環境はMacなのでhomebrew経由でインストールしました
brew install pandoc
また、pandocでPDFを出力するためにwkhtmltopdfを追加でインストールしています
brew cask install wkhtmltopdf
これで準備は完了です
pandocでmarkdownをPDFに出力する
適当なマークダウンを用意します
これは00.mdのテストです
- foo
- bar
```python
import zen
def __name__ == "=__main__":
print('これはテストです')
```
コマンドを実行します
後にCSSを適用したいので、-tでhtmlを経由しているのがポイントです
pandoc src/00.md -f markdown -t html5 -o ./output.pdf
出力結果

いい感じです
ディレクトリ横断して1ファイルに出力する
まとまった文章を書くときは章立てして書くことが多いと思います。そこで1ファイル1章を想定してそれぞれにファイルを分割し、けれども出力は1ファイルのような構成を考えてみます。
先程のファイルをコピーして、構成は以下のようにしてみました。
tree
.
└─── src
├── 00
│ ├── 00.md
│ └── 01.md
└── 01
└── 00.md
markdownで章の表現は#
なので、先程のファイルの先頭行に#
の行をそれぞれ追加します。
# テスト
これは00/00.mdのテストです
- foo
- bar
```python
import zen
def __name__ == "=__main__":
print('これはテストです')
```
pandocでは入力のファイルをglobで指定できるので以下のコマンドで1ファイルに出力できます
pandoc src/**/*.md -f markdown -t html5 -o ./output.pdf
さらに章番号を表示する-Nオプションを付与します
pandoc src/**/*.md -f markdown -t html5 -o ./output.pdf -N

見た目の調整
これで最低限出力は出来ました。
ここからは見た目の調整や改ページの制御をします。
コードブロックの背景色を変更する
ハイライトのスタイルをtangoに変更すると背景色が変わります。オプションに--highlight-style=tangoを付与します。
pandoc src/**/*.md -f markdown -t html5 -o ./output.pdf -N --highlight-style=tango

独自のCSSを当てる
よくmarkdownは表現力が弱いと言われますが、その点については独自にCSSを当てることで解消できます。
markdown内でhtmlタグを書いてしまえばclassを付与できるので、ここからはHTML + CSSの世界で整形できます。
ここでは適当にborderで囲っています
<div class="question">
以下のpythonコードの出力を答えよ
</div>
.question {
border: 1px solid black;
border-radius: 4px;
padding: 4px;
}
コマンド実行時にCSSを指定します
pandoc src/**/*.md -f markdown -t html5 -c style.css -o ./output.pdf -N --highlight-style=tango

コードブロックに任意のキャプションを表示する
Qiitaではコードブロックで
```md:src/00/00/md
のように書くとキャプションを付与できます。

便利なのでこれを実現してみます。
pandocでは{}
で様々なことができますが、その中の一つにhtmlに属性を付与する機能があります。
以下のように書いた場合、コードブロックの言語はpython、コードブロックのdata-caption属性に'test.py'を付与するようになります。
```{.python caption="test.py"}
import math
def __name__ == "=__main__":
print('これはテストです')
```
また、pandocではコードブロックの言語を指定したとき.sourceCodeクラスを付与します。
これを活用してCSSでdata-caption属性を表示すると以下のようになります。
.sourceCode:before{
font-size: small;
content:attr(data-caption);
color: #eee;
padding: 2px;
background-color: #999;
}

コードブロックに行数を表示する
これも {}
で設定可能です。
.numberLines、startFromを活用します。
``` { .python .numberLines caption="test.py"}
import math
def __name__ == "=__main__":
print('これはテストです')
```
4行目だけ表示します
``` { .python .numberLines startFrom='4'}
print('これはテストです')
```

任意の箇所で改ページする
これもCSSで表現可能です。
空のdivタグを任意の改ページしたい箇所に挿入します
<div class="page-break"></div>
.page-break {
page-break-before:always;
}
fontを変える
繰り返しですがCSSで表現可能な値は調整可能です
body {
font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Helvetica Neue", HelveticaNeue, "游ゴシック体", YuGothic, "游ゴシック Medium", "Yu Gothic Medium", "游ゴシック", "Yu Gothic", Verdana, "メイリオ", Meiryo, sans-serif;
}

所感
ややCSSでのマークアップ知識は必要ですが、CSSを適用できると技術書の執筆だけに留まらないのかと思いました(e.g. 学校のテスト問題など)
ちょっと触っただけでもpandocの多機能さ = オプションの多いことがわかりました。やりたいことがあるときはまずオプションを漁ってみるのがいいと思います。
参考
多様なフォーマットに対応!ドキュメント変換ツールPandocを知ろう - Qiita
Pandoc ユーザーズガイド 日本語版 - Japanese Pandoc User's Association
Inner Journeys: PandocのHTML出力でGFMとカスタムテンプレートを使う
Pandoc Markdown のあまり知られていない書法 - Chienomi
【 content:attr() 】CSSでタグのdata属性やtitle属性を取得して表示する | 8bit モノづくりブログ|Web制作、Webサービスに関するコラム|東京都渋谷区のWeb制作会社 株式会社8bit
VScodeのMarkdownからPDF変換時に改ページを挿入 - Qiita
Markdownをgithub-markdown.cssを充てつつwkhtmltopdfでpdfに変換 - Qiita
markdown + pandoc で、1問1答の小テストプリントを作る - adbird(広告鳥) 備忘録