#!/usr/bin/env python3
"""
2つのMarkdownファイルをPDFに変換するスクリプト
reportlab + markdown を使用
"""
import markdown
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import re
import os
def md_to_pdf(input_path, output_path):
"""MarkdownファイルをPDFに変換する"""
print(f"Converting {input_path} -> {output_path}")
with open(input_path, 'r', encoding='utf-8') as f:
content = f.read()
# スタイル設定
styles = getSampleStyleSheet()
# カスタムスタイル
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=20,
spaceAfter=12,
textColor=colors.HexColor('#1a1a2e'),
fontName='Helvetica-Bold',
)
h1_style = ParagraphStyle(
'CustomH1',
parent=styles['Heading1'],
fontSize=16,
spaceAfter=8,
spaceBefore=16,
textColor=colors.HexColor('#0d3b66'),
fontName='Helvetica-Bold',
)
h2_style = ParagraphStyle(
'CustomH2',
parent=styles['Heading2'],
fontSize=13,
spaceAfter=6,
spaceBefore=12,
textColor=colors.HexColor('#1b4f72'),
fontName='Helvetica-Bold',
)
h3_style = ParagraphStyle(
'CustomH3',
parent=styles['Heading3'],
fontSize=11,
spaceAfter=4,
spaceBefore=8,
textColor=colors.HexColor('#2e86c1'),
fontName='Helvetica-Bold',
)
body_style = ParagraphStyle(
'CustomBody',
parent=styles['Normal'],
fontSize=9,
spaceAfter=4,
leading=14,
fontName='Helvetica',
)
code_style = ParagraphStyle(
'CustomCode',
parent=styles['Code'],
fontSize=7.5,
spaceAfter=4,
spaceBefore=4,
leading=11,
fontName='Courier',
backColor=colors.HexColor('#f5f5f5'),
borderPadding=(4, 4, 4, 4),
)
bullet_style = ParagraphStyle(
'CustomBullet',
parent=styles['Normal'],
fontSize=9,
spaceAfter=2,
leading=13,
leftIndent=12,
fontName='Helvetica',
bulletIndent=4,
)
# ドキュメント作成
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
rightMargin=20*mm,
leftMargin=20*mm,
topMargin=20*mm,
bottomMargin=20*mm,
)
story = []
lines = content.split('\n')
in_code_block = False
code_lines = []
i = 0
while i < len(lines):
line = lines[i]
# コードブロック開始/終了
if line.strip().startswith('```'):
if in_code_block:
# コードブロック終了
code_text = '\n'.join(code_lines)
# 長すぎる行を折り返す
wrapped_lines = []
for cl in code_lines:
if len(cl) > 90:
cl = cl[:87] + '...'
wrapped_lines.append(cl.replace('&', '&').replace('<', '<').replace('>', '>'))
code_text_safe = '\n'.join(wrapped_lines)
if code_text_safe.strip():
try:
story.append(Paragraph(f'<font name="Courier" size="7.5"><pre>{code_text_safe}</pre></font>', code_style))
except:
story.append(Spacer(1, 2*mm))
code_lines = []
in_code_block = False
else:
in_code_block = True
i += 1
continue
if in_code_block:
code_lines.append(line)
i += 1
continue
# 空行
if not line.strip():
story.append(Spacer(1, 2*mm))
i += 1
continue
# 水平線
if line.strip().startswith('---') and len(line.strip()) >= 3:
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#cccccc'), spaceAfter=4, spaceBefore=4))
i += 1
continue
# 見出し
if line.startswith('# '):
text = line[2:].strip()
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
try:
story.append(Paragraph(text_safe, title_style))
except:
pass
elif line.startswith('## '):
text = line[3:].strip()
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
try:
story.append(Paragraph(text_safe, h1_style))
except:
pass
elif line.startswith('### '):
text = line[4:].strip()
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
try:
story.append(Paragraph(text_safe, h2_style))
except:
pass
elif line.startswith('#### '):
text = line[5:].strip()
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
try:
story.append(Paragraph(text_safe, h3_style))
except:
pass
# 箇条書き
elif line.strip().startswith('- ') or line.strip().startswith('* '):
text = re.sub(r'^[\s\-\*]+', '', line).strip()
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
# **bold** 処理
text_safe = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text_safe)
text_safe = re.sub(r'`(.+?)`', r'<font name="Courier">\1</font>', text_safe)
indent = len(line) - len(line.lstrip())
left = 12 + (indent // 2) * 8
bs = ParagraphStyle('BulletIndent', parent=bullet_style, leftIndent=left)
try:
story.append(Paragraph(f'• {text_safe}', bs))
except:
pass
# 番号付きリスト
elif re.match(r'^\d+\. ', line.strip()):
text = re.sub(r'^\d+\. ', '', line.strip())
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
text_safe = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text_safe)
try:
story.append(Paragraph(text_safe, bullet_style))
except:
pass
# 引用
elif line.strip().startswith('> '):
text = line.strip()[2:]
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
qs = ParagraphStyle('Quote', parent=body_style, leftIndent=12, textColor=colors.HexColor('#555555'), borderPadding=(2, 2, 2, 8))
try:
story.append(Paragraph(f'<i>{text_safe}</i>', qs))
except:
pass
# テーブル行はスキップ(複雑なため)
elif line.strip().startswith('|'):
text_safe = line.replace('&', '&').replace('<', '<').replace('>', '>')
try:
story.append(Paragraph(f'<font name="Courier" size="7">{text_safe}</font>', body_style))
except:
pass
# 通常テキスト
else:
text = line.strip()
if not text:
i += 1
continue
text_safe = text.replace('&', '&').replace('<', '<').replace('>', '>')
text_safe = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text_safe)
text_safe = re.sub(r'`(.+?)`', r'<font name="Courier">\1</font>', text_safe)
text_safe = re.sub(r'\*(.+?)\*', r'<i>\1</i>', text_safe)
try:
story.append(Paragraph(text_safe, body_style))
except:
pass
i += 1
# PDF生成
doc.build(story)
print(f" -> Done: {output_path}")
if __name__ == '__main__':
md_to_pdf('/home/ubuntu/MASTER_CLAUDE.md', '/home/ubuntu/MASTER_CLAUDE.pdf')
md_to_pdf('/home/ubuntu/START_PROMPT.md', '/home/ubuntu/START_PROMPT.pdf')
print("All PDFs generated successfully!")
変換スクリプト: 2つのMarkdownをPDF化(markdown+reportlab)
元ファイル: システム要件定義の分析と汎用化方法/convert_to_pdf.py
要約
汎用的に2つのMarkdownファイルをPDFへ変換するPythonスクリプト。markdownライブラリとreportlabを組み合わせ、タイトル/H1/H2などのカスタムスタイルを定義してA4 PDFを生成する。TTFontによる日本語フォント埋め込みにも対応する構成。
要点
- md_to_pdf関数で入力Markdownを読み込みPDF出力
- CustomTitle/CustomH1/CustomH2等のParagraphStyleを定義
- markdown + reportlab platypusの組み合わせ
- pdfmetrics/TTFontで日本語フォント対応
- 汎用変換ユーティリティとして複数文書に再利用