エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Glyph Bitmap Distribution Format (BDF) を Elixir で読み込む

Glyph Bitmap Distribution Format (BDF) というフォントフォーマットがあります。

en.wikipedia.org

記事の最後の方に書いたような理由があって、 BDF を読み込むパッケージを書いています。

道半ばなのですが、お試しで使えるくらいにはまとまったので、一旦出力しておこうと思った次第。

github.com

今回書いた BDF をパースする方法は、仕様上は安全でない可能性があるのですが、それに関しては記事を改めて考察することにしたいと思います。

BDF をコンソールに表示してみる

これを使って実際に文字を表示してみます。 まずは iex でお手軽に試します。

パッケージをインストールする

まだ hex.pm に公開していないので、GitHubリポジトリを指定してインストールしてください。

mix.exs でインストールする場合:

  defp deps do
    [
      {:bdf, github: "mattsan/bdf"}
    ]
  end

iex やスクリプトファイルでインストールする場合:

Mix.install([{:bdf, github: "mattsan/bdf"}])

フォントファイルを入手する

BDF ファイルを用意して読み込みます。

今回はパブリックドメインで公開されている東雲フォントを利用させていただきました。

openlab.ring.gr.jp

表示したいフォントデータを取得する

東雲フォントは文字コードに JIS を利用しています。 必要に応じて JIS X 0213のコード対応表 などを利用して表示したい文字の文字コードを確認し、フォントデータを取得します。

今回はサンプルということで、特に効率などを考慮せず Enum.find/2 で線形検索しています。

{:ok, fonts} = BDF.load("path/to/shnmk16.bdf") # 入手した BDF ファイルのパスを指定します

font = Enum.find(fonts, & &1.encoding == 0x4E6E)

表示する

取得したフォントデータは、点の一つ一つが 1 ビットで表現されているので、それらのビットを見える形に展開して表示します。

Enum.each(font.bitmap, fn row ->
  for <<dot::1 <- <<row::size(font.bbx.bbw)>> >> do
    case dot do
      0 -> " ."
      1 -> "@@"
    end
  end
  |> Enum.join()
  |> IO.puts()
end)

結果。

 . . . . . . . . . . . . . . . .
 . .@@@@@@@@@@@@@@@@@@@@@@@@ . .
 . . . . . . .@@ . . . . . . . .
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .
 .@@ . . . . .@@ . . . . . .@@ .
 .@@@@@@@@@@ .@@ .@@@@@@@@ .@@ .
 .@@ . .@@@@@@@@ . .@@@@@@@@@@ .
 . . . . . . . . . . . . . . . .
 . . .@@@@@@@@@@@@@@@@@@@@ . . .
 . . . . . . . . . . . . . . . .
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .
 . . . . . .@@ . .@@ . .@@ . . .
 . . .@@ . .@@ . .@@ . .@@ . . .
 . . . .@@ .@@ . .@@ .@@ . . . .
 .@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .
 . . . . . . . . . . . . . . . .

BDF を Livebook で表示してみる

Livebook を使えば簡単に画像で確認することもができるので、これもやってみます。

パッケージをインストールする

mattsan/bdf の他に、画像を表示すために kino と、PNG データを生成するために拙作の pngex も合わせてインストールします。

Mix.install([
  {:kino, "~> 0.12.3"},
  {:pngex, "~> 0.1.2"},
  {:bdf, github: "mattsan/bdf"}
])

BDF ファイルを読み込む

読み込みは iex で試したときと変わりません。 ダウンロードした BDF ファイルを読み込みます。

{:ok, fonts} = BDF.load("path/to/shnmk16.bdf")

フォントデータを画像に変換する

pngex を使って画像に変換します。 ここではパレットカラーを使い、 1 ビット / ドットの画像を生成しています。

palette = [
  {255, 255, 255}, # 背景: 白
  {0, 0, 0}        # 文字: 黒
]

font1 = Enum.find(fonts, & &1.encoding == 0x4E6E)
font2 = Enum.find(fonts, & &1.encoding == 0x4C74)

bitmap =
  for row <- font1.bitmap ++ font2.bitmap, into: <<>> do
    <<row::size(font1.bbx.bbw)>>
  end

Pngex.new()
|> Pngex.set_type(:indexed)
|> Pngex.set_depth(:depth1)
|> Pngex.set_size(font1.bbx.bbw, font1.bbx.bbh + font2.bbx.bbh)
|> Pngex.set_palette(palette)
|> Pngex.generate(bitmap)
|> IO.iodata_to_binary()

Livebook での表示の様子。

表示が小さいので縦横 4 倍のサイズにしてみます。

palette = [
  {255, 255, 255}, # 背景: 白
  {0, 0, 0}        # 文字: 黒
]

font1 = Enum.find(fonts, & &1.encoding == 0x4E6E)
font2 = Enum.find(fonts, & &1.encoding == 0x4C74)

bitmap =
  for row <- font1.bitmap ++ font2.bitmap, into: <<>> do
    line =
      for <<dot::1 <- <<row::size(font1.bbx.bbw)>> >>, into: <<>> do
        case dot do
          0 -> <<0::4>>
          1 -> <<0xF::4>>
        end
      end
    String.duplicate(line, 4)
  end

Pngex.new()
|> Pngex.set_type(:indexed)
|> Pngex.set_depth(:depth1)
|> Pngex.set_size(font1.bbx.bbw * 4, (font1.bbx.bbh + font2.bbx.bbh) * 4)
|> Pngex.set_palette(palette)
|> Pngex.generate(bitmap)
|> IO.iodata_to_binary()

解像度の低いディスプレイに似合いそうな表示が得られました。

そんなわけで、Nerves 再起動

開発が進んでいる様子のコメントがされつつも、なかなか公開に至っていなかった Circuits.GPIO ですが、満を持してバージョン 2.0 が今月公開されました。

hex.pm

それに刺激を受けて、5 年ほど放置していた Nerves プログラミングを再開。

nerves-project.org

(デバイスRPI-ZERO-WH Raspberry Pi Zero WH【ピンヘッダ実装済】 + WaveShare 13891 1.44インチ 128×128 LCDディスプレイHAT for RaspberryPi

そのうち、何かおもしろいものを出力できるといいな。 そのうち。

いつか読むはずっと読まない:神経網計画

nextpublishing.jp pragprog.com pragprog.com