【Python】マークダウン形式のファイルをPDF形式のファイルに変換する

本記事の題名の通り、Markdown形式のファイルをPDF形式のファイルに変換する方法を調べたので、その備忘録をこちらに残します。
今回使用したのはPythonで、使用したライブラリなどは以下の通りです。
```requirements.txt
Python 3.11.1
beautifulsoup4==4.11.2
Markdown==3.4.3
pdfkit==1.0.0
```
使用用途は以下の通り：
 - `beautifulsoup4`はMarkdown形式のファイルを一度HTMLに変換した際に、HTMLのフォーマットを整形する際に使用
 - `Markdown`はMarkdown形式のファイルの中身をHTMLに変換した文字列に変換するために使用
 - `pdfkit`はHTML形式のファイルをPDF形式のファイルに変換するために使用

さらに、`pdfkit`でHTMLからPDFへの変換を行う際に、[wkhtmltopdf](https://wkhtmltopdf.org/)を内部的に使用しているためこちらのインストールも**必要**です。

また、本記事を書くにあたり参考にした記事が以下の通りです。

https://qiita.com/t_okkan/items/2efcb35c43f2bb29858e

https://stackoverflow.com/questions/73599970/how-to-solve-wkhtmltopdf-reported-an-error-exit-with-code-1-due-to-network-err

1つ目の記事をよく参考にさせていただきました。2つ目の記事についてはHTMLからPDFに変換する際に生じたエラーを調べた際にたどり着いたものです。

本記事は以下の構成で紹介します。
1. MarkdownをHTMLに変換
2. HTMLをPDFに変換
3. MarkdownからPDFまで一気に変換
4. まとめ

# 1. MarkdownをHTMLに変換
Markdownファイルは拡張子`*.md`で作成することができます。ここではサンプルとして、`sample.md`とします。
```sample.md
# Markdown sample
* list1
* list2
* list3
```
Pythonによりこのファイルを読み取り、`markdown#markdown`によりHTML形式のテキストに変換します。
以下にサンプルコードを記載（Pythonコードと同階層に作成した場合を仮定。）：
```python
from markdown import markdown

with open("sample.md", mode="r", encoding="utf-8") as f:
    html_content = markdown(f.read())
with open("sample.html", mode="w", encoding="utf-8") as w:
    w.write(html_content)
```
```sample.html
<h1>Markdown sample</h1>
<ul>
<li>list1</li>
<li>list2</li>
<li>list3</li>
</ul>
```

# 2. HTMLをPDFに変換
1.の工程まででMarkdown形式のファイルからHTML形式のファイル`sample.html`を作成することができました。
次に、HTML形式のファイルからPDF形式のファイルを作ります。そのために`pdfkit`を使用します。ここで、注意点として[wkhtmltopdf](https://wkhtmltopdf.org/)がインストール済みであることを仮定します。環境変数を設定していない場合は、どこにインストールされているかを明示的に設定する必要があります。インストール手順に沿っていった場合は、
```
ENV_PATH = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe' 
```
にインストール済みとなっているかと思います。
ここまで確認ができれば、後は変換のプロセスのみです。HTMLを変換する際に
 
 1. ファイルから作成する `pdfkit#from_file`
 2. URLから作成する `pdfkit#from_url`
 3. テキストから作成する `pdfkit#from_string`

のオプションがありますが、今回はついでにHTMLファイルを作成したので1つ目のメソッドを使用しました。
`pdfkit#from_file`を用いて、次のように変換を行います：
```python
from pdfkit import configuration, from_file

# pdfkitライブラリを使用するために「wkhtmltopdf」が必要
ENV_PATH = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe' 
config = configuration(wkhtmltopdf = ENV_PATH)
from_file("sample.html","sample.pdf",configuration =config)
```
ここで先ほど注意した、`wkhtmltopdf`の場所をメソッド内で指定することをお忘れなく。
こちらのコードを実行し、同階層に`sample.pdf`があれば成功です。

# 3. MarkdownからPDFまで一気に変換
1.と2.で行ったことをまとめ、それに加えてMarkdown内でコードが記載されていた場合に、シンタックスハイライトが付くように設定しました。シンタックスハイライトにはCDNを使用しており、HTML内で以下のタグを追加しています。詳しくは[こちら](https://highlightjs.org/usage/
)を参照のこと。

```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
```

https://highlightjs.org/usage/

さて、雑ではありますが最終的な成果物がこちらです：
```python
from pdfkit import configuration, from_file
from markdown import markdown
from bs4 import BeautifulSoup

# pdfkitライブラリを使用するために「wkhtmltopdf」が必要
ENV_PATH = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe' 
config = configuration(wkhtmltopdf = ENV_PATH)
options = {
    'margin-top': '0.2in',
    'margin-right': '0.2in',
    'margin-bottom': '0.2in',
    'margin-left': '0.2in',
    'encoding': "UTF-8",
    'enable-local-file-access': '',
}

def html_formatting(html_content):
    html_text = ''
    html_text += '<html lang="ja"><meta charset="utf-8"><body>'
    html_text += '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">'
    html_text += '<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>'
    html_text += '<script>hljs.highlightAll();</script>'
    html_text += html_content
    html_text += '</body></html>'
    soup = BeautifulSoup(html_text, "html.parser")
    return soup.prettify()

def markdown_to_pdf(input_file, output_file):

    with open(input_file, mode="r", encoding="utf-8") as f:
        html_content = markdown(f.read(), extensions=["extra", "nl2br", "toc", "smarty"]) # md to html string
        html_text = html_formatting(html_content)
        output_html = f"{output_file}_html.html"
        output_pdf = f"{output_file}_pdf.pdf"
        with open(output_html, "w", encoding="utf-8") as w:
            w.write(html_text)
        from_file(output_html,output_pdf,configuration =config, options=options) # html to pdf


if __name__ == "__main__":
    markdown_file = "flet.md"
    pdf_file = 'test' 
    markdown_to_pdf(markdown_file, pdf_file)
```
このコードではMarkdownファイルからHTMLおよびPDFファイルをそれぞれ出力します。

# 4. まとめ
MarkdownからHTMLおよびPDFファイルを出力するコードを作成することができましたが、内でのCDNがPDFでは反映されていないようなので、今後の課題として残っています。
いつか修正します。