FONTX
ESP32

概要

Arduino core for the ESP32を使い、ESP-WROOM-32で日本語フォント(FONTX形式)を利用するための実験です。

ESP-WROOM-32で日本語を表示したいと思い、ネットを調べていたところ、FONTX形式のフォントを表示する「Arduino Unoで漢字を扱う(FONTX形式のフォントデータの読み込み・表示)」というとても分かりやすくまとめられていたウェブページを見つけました。この記事を参考に、ESP-WROOM-32で日本語フォント(FONTX)を扱うためのライブラリを作成しました。

今回作成したのは、グリフを取得するだけのライブラリなので、表示のための実装は、表示装置に合わせて別途作成する必要があります。とはいえ、グリフが取得できてしまえば、結構容易に実現できます。

フォントファイルは、例えば、以下のサイトで公開されています。

日本語フォントファイルは大きいので、SPIFFSに置くことにしました。ただし、ライブラリ自身はできるだけファイルシステムに依存しないような実装としました。SPIFFSの使い方はSPIFFSこちらを参照してください。

最初のキャプチャは、Arduino IDEのコンソールに表示したときの例です。その次は、毎日新聞のRSSデータを取得して、電子ペーパーモジュールに表示したところです。黄色い帯の部分は、FONTXではなく、電子ペーパーモジュールのライブラリが提供しているASCIIフォントです。

Arduino core for the ESP32のインストールのページはこちら

実験

ファイルフォーマット

ファイルフォーマットは、「Arduino Unoで漢字を扱う(FONTX形式のフォントデータの読み込み・表示)」に詳細・かつわかりやすく記述されています。備忘のために以下に記載しておきます。

半角フォント   全角フォント
オフセット サイズ 内容   オフセット サイズ 内容
0 6 FONTX2   0 6 FONTX2
6 8 フォント名   6 8 フォント名
14 1 フォント幅(ドット)   14 1 フォント幅(ドット)
15 1 フォント高(ドット)   15 1 フォント高(ドット)
16 1 コードフラグ(0)   16 1 コードフラグ(1)
17~ フォントデータ   17 1 コードブロック数
  18 2 コードブロック開始コード
  19 2 コードブロック終了コード
  2 コードブロック数分繰り返し
  2
  18 + 4 x コードブロック数 フォントデータ

FONTXで利用する漢字コードは、シフトJISです。

実装

Arduino core for the ESP32では、ファイルシステム関連のライブラリは、FSクラスを継承するようなつくりとなっています。このため、今回作成したライブラリは、内部ではFSクラスだけを利用し、実際の実装については、パラメータで渡すような作りとしました。今回はSPIFFSで試しましたが、おそらく、SDカードを利用したときも利用できるのではないかと思います。ただし、実際に試してはいません。

Arduino Unoの場合は、このような構造になっていないようなので、ファイルシステムの実装ごとにライブラリを作らなければならないかもしれません。詳しくは見ていません。

このライブラリ自身は、とても簡単で、以下の機能だけを持ちます。実際に使うためには、表示を行うためのライブラリ等で、取得したグリフを表示する必要があります。

  • コンストラクタ
  • begin()
  • end()
  • getGlyph()

fontxClass.h(ヘッダファイル)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma once
 
#include <FS.h>
 
typedef struct {
  uint8_t width;      // Font width
  uint8_t height;     // Font height
  uint8_t codeFlag;   // Code flag: 0 -> 1byte font, 1 -> 2byte font
  uint8_t numCodeBlocks; // Number of code block
  uint8_t glyphSize;  // Glyph size: per character size
  fs::File fs;      // File structure for font file
} fontxFile_t;
 
class fontxClass {
  public:
    fontxClass();
    bool begin(FS *fs, const char *hfile, const char *zfile);
    void end();
    bool getGlyph(uint16_t code, uint8_t *width, uint8_t *height, uint8_t *glyph);
  private:
    fontxFile_t fonts[2];
    bool openFontx(FS *fs, const char *filepath, int codeFlag);
    int numFonts;
};

fontxClass.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "fontxClass.h"
 
fontxClass::fontxClass() {
  numFonts = 0;
}
 
bool fontxClass::begin(FS *fs, const char *hfile, const char* zfile) {
  if (!numFonts && openFontx(fs, hfile, 0) && openFontx(fs, zfile, 1)) {
    return true;
  } else {
    return false;
  }
}
 
void fontxClass::end() {
  numFonts = 0;
  for (int i = 0; i < 2; i++) {
    fonts[i].fs.close();
  }
}
 
bool fontxClass::openFontx(FS *fs, const char *filepath, int codeFlag) {
  char buf[18];
 
  if (fonts[codeFlag].fs = fs->open(filepath, "r")) {
    fonts[codeFlag].fs.readBytes(buf, sizeof(buf));
    fonts[codeFlag].width = buf[14];
    fonts[codeFlag].height = buf[15];
    fonts[codeFlag].codeFlag = buf[16];
    fonts[codeFlag].numCodeBlocks = buf[17]; // for multibyte character set
    fonts[codeFlag].glyphSize = (fonts[codeFlag].width + 7) / 8 * fonts[codeFlag].height;
 
    if (fonts[codeFlag].codeFlag != codeFlag) {
      end();
      return false;
    }
 
    numFonts++;
    return true;
  }
 
  return false;
}
 
bool fontxClass::getGlyph(uint16_t scode, uint8_t *width, uint8_t *height, uint8_t *glyph) {
  uint8_t ncb;
  uint16_t startCode, endCode;
  uint8_t fontIndex;
  uint16_t numChars = 0;
  uint32_t offset = 0;
  uint8_t index;
 
  if (scode < 0x100) { // single-byte character
    index = 0;
    offset = 17 + scode * fonts[index].glyphSize;
  } else {             // multibyte character
    index = 1;
    ncb = fonts[index].numCodeBlocks;
 
    fonts[index].fs.seek(18);
    while (ncb--) {
      fonts[index].fs.readBytes((char *)&startCode, 2);
      fonts[index].fs.readBytes((char *)&endCode, 2);
      if ((scode >= startCode) && (scode <= endCode)) {  // found the code
        numChars += scode - startCode;
        offset = 18 + 4 * fonts[index].numCodeBlocks + numChars * fonts[index].glyphSize;
        break;
      }
      numChars += endCode - startCode + 1; // add the number of characters in the code block
    }
  }
 
  if (offset) {
    fonts[index].fs.seek(offset);
    fonts[index].fs.readBytes((char *)glyph, fonts[index].glyphSize);
    *width = fonts[index].width;
    *height = fonts[index].height;
    return true;
  }
 
  return false;
}

サンプルプログラム

Arduino IDEに、「A」と「あ」を表示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <SPIFFS.h>
#include "fontxClass.h"
 
fontxClass fx;
 
void drawChar(uint8_t *glyph, uint8_t width, uint8_t height) {
  for (int j = 0; j < height; j++) {
    for (int i = 0; i < width; i++) {
      if (*glyph & (0b10000000 >> (i % 8))) {
        Serial.print("*");
      } else {
        Serial.print(" ");
      }
      if (i % 8 == 7) {
        glyph++;
      }
    }
 
    if (width % 8 != 0) {
      glyph++;
    }
    Serial.println("");
  }
}
 
void setup() {
  uint8_t glyphBuffer[32];
  uint8_t width, height;
 
  Serial.begin(115200);
  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS failed.");
  }
 
  fx.begin(&SPIFFS, "/ILGH16XB.FNT", "/ILGZ16XF.FNT");
 
  fx.getGlyph(0x41, &width, &height, glyphBuffer);
  drawChar(glyphBuffer, width, height);
  fx.getGlyph(0x82a0, &width, &height, glyphBuffer);
  drawChar(glyphBuffer, width, height);
}
 
void loop() {
 
}

バージョン

Hardware:ESP-WROOM-32
Software:Arduino 1.8.4/Arduino core for the ESP32

最終更新日

March 21, 2022

inserted by FC2 system