Markdown から HTML と PDF を作る

目次

講義記録や業務文書を書くにあたって,ひとつのファイルから HTML と PDF をそれなりの見た目で作れるように VSCodeMarkdown PDF を用いてテンプレートを作った。 これをまとめる。

背景

年度替わりにあたって,長らく情報の授業を持っていただいていた同僚が異動なさった。それに伴い,この1年は私が情報の授業を4単位分ほど担当することとなった。

情報を持つのは6年ぶりであり,カリキュラムも変わっている。 何より,生徒が1人ずつ端末を持っている。 そこで,復習しやすいようにウェブサイト上に講義記録を残すことにした。 少しでも手間が小さくなるよう,原稿は Markdown で書き VSCode によって HTML を作る。また,一部を印刷して配りたくなるかもしれないので,そのまま PDF にもできるようにしたい。

解決

Markdown PDF の導入

Markdown から HTML と PDF を作るには Markdown PDF を用いる。

VSCode を導入していれば,[拡張機能]から markdown pdf と検索してすぐに現れる。似た名前のものがいくつかあるが,ダウンロード数や評価を見ればよい。作者は yzane 氏である。

その他の VSCode 環境

Markdown の入力支援として Markdown All in One をインストールしている。

スニペットの入力支援として Easy Snippet をインストールしている。

これらは導入しなくとも差しつかえないが,一部の記述はこれらを踏まえている。

VSCode における Markdown PDF の設定

Footer Template はそのまま用いることとした。

Header Template は修正しやすいよう,style='display: none; のみを加えて非表示とした。

<div style="font-size: 9px; margin-left: 1cm;"> <span class='title' style='display: none;'></span></div> <div style="font-size: 9px; margin-left: auto; margin-right: 1cm; display: none;">%%ISO-DATE%%</div>

VSCode における Markdown のスニペット

改行位置の指定

印刷するにあたっての改行位置を指定するため,次のようにした。

// @prefix pagebreak-pdf
// @description 

<div style="page-break-before:always"></div>

スタイルシート

ここで,CSS を読み込む機能があるようだが,校内のファイル共有システムなどを踏まえ,オフライン環境でも扱えるようスタイルシートはファイルに直に書き込むこととした。いずれ見映えを変えたくなったときに使いまわしにくいことには気をつけたい。

  • 画面表示としても PDF としても見やすく,配置が大きく変わらないよう,間を取ってデザインした。
    • PDF は A4 用紙を想定している。
    • max-width: 600px;padding: 0 26px; を指定している。
    • 白黒印刷でも見やすいようデザインした。
  • em は圏点としている。em の意味からいえば,日本語では圏点がふさわしいだろう。
  • MathJax を使えるようにしている。要らなければ <script ...から下を消せばよい。
// @prefix css
// @description
<style>
/* CSS for Markdown to html/PDF 20250507*/
html, body {
  font-family: "Yu Gothic", "YuGothic", "Hiragino Kaku Gothic ProN",
"Hiragino Sans", sans-serif;
  /* var(--markdown-font-family, -apple-system, BlinkMacSystemFont,
"Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans",
sans-serif);*/
  font-size: 15px; /* var(--markdown-font-size, 14px);*/
  padding: 0 26px;
  line-height: var(--markdown-line-height, 22px);
  word-wrap: break-word;
}
body {
  font-family:  "Yu Gothic", "YuGothic", "Hiragino Kaku Gothic ProN",
"Hiragino Sans", sans-serif;
  /* -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI',
system-ui, 'Ubuntu', 'Droid Sans', sans-serif; */
  font-size: 15px; /* var(--markdown-font-size, 14px);*/
  line-height: 1.6;
  /* padding-top: 1em; */
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
}
h1, h2, h3, h4, h5, h6, p, ol, ul, pre {
  margin-top: 0;
}
h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  margin-top: 24px;
  margin-bottom: 16px;
  line-height: 1.25;
}
h1 {
  font-size: 1.75em;
  margin-top: 0;
  padding-bottom: 0.1em;
  border-color: rgba(0, 0, 0, 0.18);
  border-bottom-width: 3px;
  border-bottom-style: double;
}
h2 {
  font-size: 1.25em;
  padding-bottom: 0.1em;
  border-color: rgba(0, 0, 0, 0.18);
  border-bottom-width: 1px;
  border-bottom-style: solid;
}
h3 {
  font-size: 1.25em;
}
blockquote {
  margin: 0;
  padding: 0px 16px 0 10px;
  border-left-width: 5px;
  border-left-style: solid;
  border-radius: 2px;
  background: transparent;
  border-color: #000000;
}
ul, ol {
  margin-bottom: 0.7em;
  margin-top: 0;
}
ul ul:first-child, ul ol:first-child, ol ul:first-child, ol ol:first-child {
  margin-bottom: 0;
}
p {
  text-indent: 1em;
  margin-bottom: 16px;
}
:not(pre):not(.hljs) > code {
  /* color: #2c73e6; */
  color: #4d4d4c;
  padding: 3px 6px;
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  border-radius: 3px;
}
code {
  /* color: #2c73e6; */
  font-family: Consolas, "Courier New", Courier, Menlo, Monaco, "Droid
Sans Mono", "Yu Gothic", "YuGothic", "Hiragino Kaku Gothic ProN",
"Hiragino Sans", sans-serif, monospace, "Droid Sans Fallback";
  /* font-weight: 600; */
  background-color: #f8f8f8;
  color: #4d4d4c;
}
pre {
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  border-radius: 3px;
  overflow-x: auto;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: #4d4d4c;
}
a {
  text-decoration: none;
  color: #2c73e6;
}
a:hover {
  text-decoration: underline;
}
/* em 強勢によって文章の意味を変えるとき
ブログを<em>半年ぶり</em>に更新しました
ブログを半年ぶりに<em>更新しました</em> */
em {
  font-style: normal;
  text-emphasis: filled dot;
}
/* strong 重要性・深刻性・緊急性
文をすべて強調するようなとき */
strong {
  font-style: normal;
  font-weight: 700;
}
</style>
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_CHTML"></script>
<script type="text/x-mathjax-config">
 MathJax.Hub.Config({
 tex2jax: {
 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
 }
 });
</script>

下にいくらか行があるのは,スニペットを読み込んだのちに本文を打つところまで空行を入れるためである。

補足

Markdown PDF のヘッダ

Markdown PDF のヘッダについては,あまり自由度が高くない。

タイトルがファイル名になってしまうため非表示とした。設定からその都度手で打てばタイトルをヘッダに出すことはできるが,続かないと考えたのでそもそも消すことを選んだ。

日付は YYYY-MM-DD 形式で悪くないのだが,PDF が作られた日になってしまい,文書を配る日にできない。設定からその都度手で打てばタイトルをヘッダに出すことはできるが,続かないと考えたのでそもそも消すことを選んだ。

参考

Markdown PDF の導入

VSCode における Markdown PDF の設定

VSCode における Markdown のスニペット

Markdown における PDF 上の改行

HTML における em と strong

CSS の圏点

等幅フォント

その他