はじめに
現役の高校教員(数学)です。エンジニアではありません。
私は自分用の資料をぜんぶMarkdownで書いています。ただ、印刷したり人に渡したりするにはPDFの方が都合がいい。そこで「Markdownを編集したら、コマンド1つでPDFが再生成される」状態を作りました。
一番ハマったのは、予想通り日本語フォントです。ただ、最終的な解決は拍子抜けするほど簡単でした。
reportlabに最初から入っているCIDフォントを使えば、フォントファイルの用意もインストールも要らない。
この記事はそこと、その先で踏んだ絵文字の地雷の話です。
環境
- macOS
- Claude Code
- Python 3
- reportlab
インストールしたのは reportlab だけです。
pip install reportlab
1. なぜ変換ツールではなくスクリプトにしたか
最初はPandocなどの既存ツールを考えました。やめた理由は2つです。
- 表のデザインを自分で決めたかった(資料の大半が表なので、ここが読みやすさを決める)
- 同じファイルを何度も再生成するので、体裁が毎回まったく同じである必要があった
要するに「変換」ではなく「毎回同じ体裁で組版される」ことが欲しかった、ということです。それならスクリプトを1本持つ方が早い、という判断でした。
2. 日本語フォント問題は、実は問題ではなかった
PythonでPDFを作るとき、日本語で必ず詰まります。ネットで調べると、だいたいこういう手順が出てきます。
- IPAフォントなどの
.ttfをダウンロードする - スクリプトから相対パスで読み込む
-
TTFontで登録する
これでも動きますが、フォントファイルをリポジトリなり作業フォルダなりに置いて回ることになります。環境が変わると壊れます。
reportlabには、日本語のCIDフォントが最初から入っています。
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5")) # ゴシック
pdfmetrics.registerFont(UnicodeCIDFont("HeiseiMin-W3")) # 明朝
GOTHIC = "HeiseiKakuGo-W5"
MINCHO = "HeiseiMin-W3"
**これだけです。**フォントファイルは1つも要りません。ダウンロードも配置も不要。
見出しをゴシック、本文を明朝にすると、それだけで「資料っぽい」見た目になります。
非エンジニアの感想として書いておくと、この手の「調べると難しい手順が出てくるが、実は標準機能で足りる」というパターンは本当によくあります。Claude Codeに「日本語フォントを追加インストールなしで使いたい」と条件を付けて聞いたら、CIDフォントを提示されました。制約を先に伝えると、解法が変わるという良い例でした。
3. 本当の地雷は絵文字だった
フォントが解決して喜んでいたら、次で詰まりました。
CIDフォントは絵文字のグリフを持っていません。
私のMarkdownには ✅ ⏳ 🔴 といった絵文字が大量に入っていました。ステータス表示に使っていたからです。これがPDFでは全部**黒い豆腐(□)**になりました。
しかもエラーは出ません。静かに化けるだけです。
対処は単純で、PDFに流す前にテキストへ置換しました。
EMOJI_MAP = {
"🔴": "[要確認]",
"🟠": "[進行中]",
"🟡": "[中]",
"🟢": "[軽微]",
"✅": "[済]",
"⏳": "[未実行]",
}
def strip_emoji(text):
for emoji, replacement in EMOJI_MAP.items():
text = text.replace(emoji, replacement)
return text
結果的に、これはPDFの方が読みやすくなりました。印刷して人に見せる資料では、色付きの丸より [要確認] という文字の方が意味が伝わるからです。
制約に合わせた結果、元より良くなることがある、という体験でした。
4. Markdownをどこまで解釈するか
自作パーサは、自分が実際に使う記法だけに絞りました。
| 記法 | 扱い |
|---|---|
# 見出し |
1個目はタイトル、2個目以降はH1(下に罫線) |
## 見出し |
H2 |
| a | b | |
表として組む(ヘッダー行を繰り返し) |
``` |
コードブロック(グレー背景) |
- 項目 |
箇条書き |
**太字** |
ゴシックへのフォント切替 |
--- |
余白 |
CommonMarkに準拠しようとすると終わりません。自分のファイルが通ればいいと割り切ったので、パーサ本体は100行程度で済みました。
太字の扱いだけ少し工夫が要ります。reportlabのParagraphはHTMLライクなタグを解釈するので、**bold** をフォント切替タグに置き換えます。
def inline_bold(text):
text = strip_emoji(text)
return re.sub(r"\*\*(.+?)\*\*", rf'<font name="{GOTHIC}">\1</font>', text)
本文が明朝なので、太字部分だけゴシックになるという仕上がりです。日本語の資料では、これが一番自然に見えました。
5. 表がきれいだと資料の質が上がる
一番効いたのはここでした。
t = Table(table_data, colWidths=[col_width] * col_count, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1E2761")), # ヘッダーは濃紺
("ROWBACKGROUNDS", (0, 1), (-1, -1),
[colors.HexColor("#F4F6FB"), colors.white]), # 交互に薄い色
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#C7CEDD")),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]))
ポイントは2つです。
-
repeatRows=1:表がページをまたいだとき、ヘッダー行が次ページの先頭にも出ます。これが無いと、2ページ目以降が何の表か分からなくなります -
ROWBACKGROUNDSで行を交互に色付け:行数の多い表で、目が横にズレなくなります
どちらも「読む人」のための設定です。技術的には1行ずつですが、印刷して読んだときの差はかなり大きいです。
6. ハマったポイントまとめ
| ハマり | 対処 |
|---|---|
| 日本語が出ない |
UnicodeCIDFont("HeiseiKakuGo-W5") を登録(ファイル不要) |
| 絵文字が豆腐になる(エラーは出ない) | PDF生成前にテキストへ置換 |
| 表がページをまたぐと何の表か分からない | Table(..., repeatRows=1) |
| コードブロックのインデントが潰れる | 半角スペースを に置換してからParagraphへ |
| 太字が効かない |
** を <font name="..."> タグに変換する |
おわりに
やったことは「Markdownを読んでreportlabに流す」だけで、200行ほどのスクリプトです。
ただ、資料をMarkdownで持てるようになったことの効果が想像以上でした。編集はテキストエディタで済み、差分が見え、PDFはいつでも同じ体裁で再生成される。Wordで管理していた頃に戻る気はもうありません。
非エンジニアにとっての本題は、たぶん「PDFを作ること」ではなく「資料の原本をプレーンテキストに置けること」の方だと思います。
次は、Excelでカンマ区切りの文字列を数式だけで分割した話を書く予定です。