こうかの雑記

こうかの雑記

昔の懐かしいこと、ubuntuのこと、その他いろいろ

放置していたホームページの更新

 先日(2020/3/4)忍者ツールズから一部サービス提供終了の案内がありました。「えっ、何か使っていたかな?」となりましたが対象のサービスがカウンタだとわかり、長らく放置していたホームページで使わせてもらっていたのを思い出しました。忍者カウンタのサービス閉鎖予定日は2021年03月01日(月)ですが、案内頂いた今しかないと思い、早々に対処しました。

 振り返ればブログに移行してからというもの殆ど放置状態でした。昨年、そのブログはサービス終了となり無くなってしまいました。またホームページの話題の中心であった趣味も、昨年、泣く泣く機材を手放し卒業しました。なのでホームページも閉鎖しても良いのですが。

 取り敢えずカウンタを外して一通り書くページを確認しましたところ、年月が経つのを感じました。リンクしていたサイトが消滅していたり、記事が現状とは違うだろうなと思われる物が多く、それらを取り除きました。残ったのは今でも通用するだろう僅かな情報と、その趣味の世界における歴史的情報のページのみとなりました。結果、とても小さなホームページとなってしまいました。

 歴史的情報は海外のページで見つけた記事を自分で翻訳したものです。その原文のページも消滅していましたので、もはや私のページしか残っていないと思います。これからこの趣味の世界に興味を持たられるだろう方には貴重な情報になると思われますので、可能な限り残しておきたいと思います。これがかつての趣味に対する私の思いの表し方です。(執着かも知れませんが) この世界に参入しようとする希少な方々の参考になれば幸いです。

 この記事を読まれた方には、この趣味が何のことかわからないと思いますが、敢えてそうしています。国産の機材がなく、とてもマイナーな趣味ということだけはお伝えできます。申し訳ありません。

ubuntuを強制ストップする

 ubuntuが珍しくハングアップしてキーボードが効かなくなりました。
 でも今回は慌てません。滅多にこういう事はないのですが、対処方法をメモってあります。

 Alt + PrintScreenを押したままにして、
  RSEIUB

 と順番にそれぞれ1〜2秒ほど押し続けます。

 これだけ書いたメモを机の上のトレーに置いていたおかげで、無事再起動できました。

Windowsを使っていた頃はCtrl+Alt+Deleteをよく使ったいたので覚えましたが。これだけ長いと覚えられませんね。)

 詳しくはhttps://wiki.ubuntulinux.jp/UbuntuTips/Others/MagicSysRqに記載されていますが、簡単にキーの意味を載せておきます。

キー  説明
R Xサーバからキーボードとマウスのコントロールを取り戻します。
S ディスクキャッシュ上のデータをすべて、ハードディスクに書き込みます。この同期作業により、データが破壊される可能性を減らすことができます。
E initを除くすべてのプロセスにSIGTERMシグナルを送り、ドキュメントなどを保存して、終了するように試みます。
I initを除くすべてのプロセスにSIGKILLシグナルを送り、プロセスを強制終了します。
U 現在マウントされているすべてのファイルシステムを、書き込み不可の状態で再マウントします。
B システムが安全に再起動できる状態かどうかを確認することなく再起動を行います。

 実際にキーボードを押すのは押し辛かったです。でもこれはAltキーを左手で押さえる癖のせいでした。
 AltキーとPrintScreenキーを右手で押せば、左手で楽に各キーを押せます。

markdownファイルをpdfファイルに変換する

 markdown記法で簡単に資料が作れるようになってブラウザでも簡単に見られるようになって印刷もできるようになれば、ワープロソフト(例えばWordとかWriterとか)の出番が少なくなると思います。多分、体裁が気になるような資料だけがワープロソフトの対象として残るだけになるのではないかと思います。

 直接印刷することは望んでなくてpdfファイルに出力できれば良いので、今回はmdファイルからpdfファイルにする機能をpythonで作成しました。 他のツールもありますが、事前の下調べに手間暇掛けて自作の方法をとりました。

必要なツールとインストール

 mdファイルからpdfファイルにするために次の様なソフトウェアがあります。

  • Python-Markdown
    これはmd記法で書かれたテキストをhtmlに変換するツールです。
  • wkhtmltopdf
    これは単独でも使えるhtmlからpdfに変換するツールです。直接にはpythonから使うことは出来ませんが、次のpdfkitを設定して通じて使うことができます。
  • pdfkit
    pythonから多くの引数を設定してwkhtmltopdfを呼び出すことが出来るます。

 この中のPython-Markdownとpdfkitをライブラリーとしてpythonでimportして使います。

 インストール方法は次の通りです。

 端末画面から次のコマンドを実行します。

$ pip3 install markdown
$ pip3 install pdfkit

 次に、https://wkhtmltopdf.org/downloads.htmlからシステムに応じたパッケージをダウンロードします。ubuntuの場合は現時点でUbuntu 18.04 (bionic) のamd64 / i386になります。。  ubuntuだとダウンロードしたファイルをダブルクリックでインストールできます。コマンドでインストールする場合は

$ sudo apt install -y wkhtmltopdf

スクリプトの仕様

  • mdファイルとそれに必要な画像ファイルは同じフォルダーに保存されているものとします。
  • cssファイルは別の場所に保存していても良いものとします。
  • GUIまたはコマンドラインでmdファイルとcssファイルを指定すると、mdファイルと同じ場所にpdfファイルを出力します。 画像を含まない場合は、htmlファイルも出力します。
  • 画像はmd記法の![ alt文字列](画像ファイル名)で書かれていると画像ファイルそのままの大きさになるので、htmlの記法で<img alt="代替え文字列" src="画像ファイル名.JPG" width="400" または height="400" >で書くことを勧めます。
  • pdfの用紙サイズは'A4'固定、余白も固定とします。
  • mdファイル中に[TOC]が書かれていると目次に置き換わります。

使い方

 ubuntuの場合、スクリプトファイルに実行権限を付けておくとGUI画面でクリックするだけで起動できます。GUIで起動した場合はmdファイルとcssファイルを画面で選択入力できます。

 コマンドラインで実行する場合はmdファイルがあるフォルダーに移動してから次の書式でコマンド入力します。(スクリプトファイル名をmd2pdf.pyとした場合)

md2pdf.py mdファイル cssファイル

 cssファイルmdファイルとは違う場所も可

例)md2pdf.py  mdfile.md  /デスクトップ/css/md2pdf.css

 この場合mdファイルとcssファイルの順番はいれかわっても大丈夫です。処理したいmdファイルが多い時はコマンドラインで実行した方が便利です。

 テストしていますが、全てのパターンをテストできたわけではありません。利用される場合は自己責任でお願いします。出力されたpdfは必ず確認して下さい。

スクリプト

 ネットで見つけた例ではPython-Markdown拡張機能の指定が['tables', 'toc']の二つを指定されているものばかりでしたが、よく使われる数字付きリストや枠付きコードブロックが正常に処理されませんでした。以下のスクリプトでは['extra', 'toc']として対応しています。

 画像ファイルの扱いで困ってネットで探したところ、posturanさんの記事を見つけました。base64関係のコードをそのまま使わせて頂きました。posturanさん、ありがとうござました。
なお、画像の表示サイズの指定をする前提でコードを書き直しています。

#!/usr/bin/env python3
'''
    mdファイルとcssファイルから、mdファイルと同じ場所にpdfファイルを作る。
'''
import markdown
import pdfkit
import codecs
import tkinter
from tkinter import messagebox
from tkinter import filedialog
import os
import re
import base64
import sys
'''
    画像ファイルをBase64エンコードテキストに
    http://oboe2uran.hatenablog.com/entry/2019/01/25/150934から拝借
'''
def imageToB64encode(path):
    with open(path, 'rb') as f:
        return base64.b64encode(f.read()).decode('utf-8')  #バイナリーをstrに
'''
    markdownファイルを読み込み、htmlテキストに変換する。
'''
def md2html(md_file):
    with codecs.open(md_file, mode = 'r', encoding = 'utf-8') as f:
        md_txt = f.read()
        md_txt = re.sub('@import ".+"\n', '', md_txt)
    html_body = markdown.Markdown(extensions = ['extra', 'toc']).convert(md_txt)
    # 画像を<img src=data:image/png;base64,base64エンコード文字列"/> にして埋め込む。
    # ループ部分は http://oboe2uran.hatenablog.com/entry/2019/01/25/150934 から拝借後
    # 画像表示サイズ指定に対応させた。
    img_sw = False
    for imgtag in re.findall('<img .* src=".+"', html_body):
        # 画像ファイルの名前を得る
        s = re.search('src=".+"', imgtag).group(0).replace('src="', '').replace('"', '')
        imgfname = s.split(' ',1)[0]    # 続くオプションをカット
        # imgval = re.search('<img .* ', imgtag).group(0)
        # imgext = imgfname[-3:]
        imgval = 'src=data:image/' + imgfname[-3:] + ';base64,' + imageToB64encode(imgfname) 
        imgsrc = 'src="' + imgfname+ '"'
        img_sw = True
        html_body = html_body.replace(imgsrc, imgval)
    return html_body, img_sw
'''
    cssを埋め込む
'''
def set_html(html_body, css_file):
    with codecs.open(css_file, mode = 'r', encoding = 'utf-8') as f:
            css_txt = f.read()
    html_txt = '<html>\n<head>\n<meta charset="utf-8">\n<style>' + css_txt + '\n</style>\n</head><boby>\n' + html_body + '\n</body>\n</html>'
    return html_txt
'''
    htmlファイルをカレントディレクトリに出力する。
'''
def output_html(md_name, html_txt):
    with open(md_name.replace('md', 'html'), 'w') as f:
        f.write(html_txt)
'''
    準備処理 対象mdファイルとそのディレクトリ、cssファイルのパスを取得
'''
def preparat_rtn(argv):
    if len(argv) == 3:
        if argv[1][-3:] == '.md':
            md_file = argv[1]
        else:
            if argv[1][-4:] == '.css':
                css_file = argv[1]
        if argv[2][-4:] == '.css':
            css_file = argv[2]
        else:
            argv[2][-3:] == '.md'
            md_file =argv[2]
        md_name = md_file
    else :
        root = tkinter.Tk()
        root.withdraw()
        tkinter.messagebox.showinfo('md2pdf.py',
                                '対象mdファイルを選択して下さい。')
        md_file = tkinter.filedialog.askopenfilename(title = 'md2pdf.py mdファイルを選択') 
        if md_file == '':
            return '', '', '', ''
        md_name = os.path.basename(md_file)
        md_dir  = os.path.dirname(md_file)
        os.chdir(md_dir)
        tkinter.messagebox.showinfo('md2pdf.py',
                                'cssファイルを選択して下さい。')
        root.destroy()
        css_file = tkinter.filedialog.askopenfilename(title = 'md2pdf.py cssファイルを選択') 
        if css_file == '':
            return '', '', '', ''
    pdf_file = md_name.replace('.md', '.pdf')
    return md_file, md_name, css_file, pdf_file

def main_rtn():
    md_file, md_name, css_file, pdf_file = preparat_rtn(sys.argv)  #準備処理
    if md_file == '':
        print('処理中断しました')
    else:
        html_body, img_sw = md2html(md_file)
        html_txt  = set_html(html_body, css_file)
        pdf_options = {'page-size': 'A4',
                       'margin-top': '0.75in',
                       'margin-right': '0.75in',
                       'margin-bottom': '0.75in',
                       'margin-left': '0.75in',
                       'encoding': "UTF-8"
        }
        # htmlからPDFを出力
        pdfkit.from_string(html_txt, pdf_file, options=pdf_options)
        if img_sw == False:     # 画像がなければhtmlを出力
            output_html(md_name, html_txt)
if __name__ == '__main__':
    main_rtn()

cssファイル

 cssは簡単なものを用意しました。
 行頭の字下げは、pタグの設定で対応しました。

body {
    font-size : 150%;
    }
p   {
    text-indent: 1em;
    }
th,td {
    border: solid 1px;  /* 枠線指定 */
    padding: 5px;      /* 余白指定 */
    }
table {
    border-collapse:  collapse; /* セルの線を重ねる */
    }
pre {
    padding: 5px;
    overflow: auto;                 /* 折り返し */
    white-space: pre-wrap;
    word-wrap: break-word;
    background-color:#EEEEEE;
    }

その他情報

 Python-Markdownでのmdからhtmlへの変換で少しmdの解釈が違うことが有りましたので、ここで紹介します。

 行頭に全角空白で字下げしても半角空白と同様に無視されます。
cssファイルの中で、pタグにtext-indent: 1em;を付加して対応しました。
余談ですが、マイクロソフトVsCodeでも行頭の字下げが無視されますが、同じことだと思います。

 リストをネストする場合、ネスト部分の字下げ数がビューワを実装した人によって解釈が違うようで、typora、VsCodeでは空白3文字でしたが、このライブラリーでは空白4文字の字下げが必要でした。また他のビューワでは空白1文字の場合もありました。

 下記は空白3文字の時の例

* リスト1
   * リスト1−1
   * リスト1−2
* リスト2

では出力されるhtmlは

<ul>
<li>リスト1</li>
<li>リスト1−1</li>
<li>リスト1−2</li>
<li>リスト2</li>
</ul>

となり、ネストされませんでした。

 mdファイルをpdfにする手段は他にもあり、次のソフトでも出来ます。

  • typora
    ゆくゆく有料ソフトになることが予定されています。
  • VsCode
    汎用の高機能テキストエディターです。マークダウンのための拡張機能にpdf出力も備えています。行頭の字下げが無視されます。cssを直すことが出来ればなぁと思います。
  • pandoc
    各種ファイル様式間でのファイルコンバーターです。色々できて便利そうなのですがコマンドラインでしか使えません。そのコマンドのパラメーターが多く慣れていない人には辛いものがあります。またエラーが出た時の対処する方法が私にはわかりませんでした。

関連情報

Using Markdown as a Python Library

Python-Markdown拡張機能

pdfkit 0.6.1

wkhtmltopdf