LovyanGFXで画面描画(12):外部から好みの日本語フォントサブセットを導入して期末試験対策カウンタを作る

LovyanGFX Advent Calendar 2022 23日目
「LovyanGFXで画面描画(10):内蔵以外のフォントを使用してデジタル時計を作る」
https://qiita.com/nanbuwks/items/13ef19e5db40197faf7f

では好みの日本語フォントをロードしましたが、大きなポイント数をつかうと、使いたい文字が1フェイスだっとしても何千ものフォントフェイスをロードすることになるので大変なことになります。

「LovyanGFXで画面描画(9):日本語フォントのうち必要なシェイプのみをデータ化」
https://qiita.com/nanbuwks/items/e09abbb90cedd93a2118
では yamamaya さんの
「日本語フォントサブセットジェネレーター for LovyanGFX」
を使い、必要なフォントフェイスだけをロードしましたが、元々 LovyanGFX に組み込まれている日本語フォントと同じ IPAフォント と efont のみが対象でした。

今日は自分の好きなフォントを使いつつ、必要なフォントフェイスだけをロードしてリソースを削減する方法です。

# 作るもの

期末試験対策本部の掲示を作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/fda69a60-91e6-4f1c-2500-4c540e678634.png)

# フォントを用意

期末試験対策本部に最適なフォントとして、

http://www.yamadera.info/fonts/fonts-index.htm

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/7bdff004-dc1b-5d22-ff05-1151db60d047.png)
の、「真宗聖典楷書」という素晴らしい書体を使用することにしました。

ダウンロードした 	SeitenKaisho.zip を以下のように作業します。

```

$ mkdir SeitenKaisho
$ cd SeitenKaisho
$ unzip ../SeitenKaisho.zip 
Archive:  ../SeitenKaisho.zip
  inflating: 真宗聖典楷書.ttf  
$ ls -ogh
合計 4.5M
-rw-rw-r-- 1 4.5M  9月  7  2004 真宗聖典楷書.ttf
$ otf2bdf -r 72 -p 24 -o SeitenKaisho_24pt.bdf 真宗聖典楷書.ttf 
$ ls -ogh
合計 6.9M
-rw-rw-r-- 1 2.4M 12月 28 19:11 SeitenKaisho_24pt.bdf
-rw-rw-r-- 1 4.5M  9月  7  2004 真宗聖典楷書.ttf
```
bdf ファイルまで変換しておきます。
今回は 32pt のものを作成しました。

```
$ otf2bdf -r 72 -p 32 -o SeitenKaisho_32pt.bdf 真宗聖典楷書.ttf 
```
# ツールを用意する

- otf2bdf
- bdfconv
- Python3

前者2つは、LovyanGFXで画面描画(11)で導入しています。

# 変換作業

必要なフォントフェイスを map ファイルで指定して、cファイルにします。
試しに、japanese3.map を使って4444文字分を c ファイルにすると以下のようになりました。

```
$ ../u8g2/tools/font/bdfconv/bdfconv -v -b 0 -f 1 -M ../japanese3.map SeitenKaisho_32pt.bdf -o SeitenKaisho_32pt.c
Parse File SeitenKaisho_32pt.bdf: 16843 glyph(s) found
Map: exclude=0 from=32/$20 to=127/$7f map=32/$20
Map: exclude=0 from=12288/$3000 to=12288/$3000 map=12288/$3000 (further single glyph logs disabled)
Reduce: Start
Reduce: End
CalculateMaxBBX: x=0, y=-5, w=32, h=33
CalculateMaxBBX: Encodings x=32, y=95, w=19998, h=124
bf_CalculateMinMaxDWidth: dx_min=16, dx_max=32
bf_CalculateMinMaxDWidth: x_min=0, x_max=23
bf_CalculateMaxBitFieldSize: bbx.x=6, bbx.y=6, bbx.w=6, bbx.h=6, dwidth=7
RLE Compress: best zero bits 4, one bits 3, total bit size 2688432
RLE Compress: Font code generation, selected glyphs=3857, total glyphs=16843
RLE Compress: ASCII gylphs=95, Unicode glyphs=3762
RLE Compress: Glyphs per unicode lookup table entry=101
RLE Compress: Unicode lookup table len=37, written entries=37
RLE Compress: Unicode lookup table first entry: delta=148, encoding=12412
RLE Compress: Unicode lookup table last entry: delta=10495, encoding=65535
RLE Compress: Unicode glyphs written=3762
RLE Compress: 'A' pos = 1034, 'a' pos = 2522
RLE Compress: Font size 336229
bf_WriteU8G2CByFilename: Write file 'SeitenKaisho_32pt.c'
```
トータルのグリフは 16843 。それから ASCII グリフ 95 文字、日本語グリフ 3762 文字分を抽出したことになります。

## map ふぁいるを作る 

使いたいのはこの表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139524/7bdff004-dc1b-5d22-ff05-1151db60d047.png)


に必要な、

"期末試験対策本部試験からまで あと0123456789日と時間分秒"

です。
これをmapファイル形式にするには、 以下の python3 のワンライナーを使います。

```
python -c "import sys;[print('$'+hex(ord(ch))[2:]+',') for ch in input()]"

```
以下のように使います。
```
$ echo あいうabc | python -c "import sys;[print('$'+hex(ord(ch))[2:]+',') for ch in input()]"
$3042,
$3044,
$3046,
$61,
$62,
$63,
```
必要な文字をファイル化していた場合は、

`echo <文字列>` の代わりに `cat <ファイル名>` とします。 (MS-Windows で行う場合は、ファイル内のエンコーディングが UTF-8 でないといけないかも知れません。)


```
$ echo "期末試験対策本部試験からまで あと0123456789日と時間分秒" | python -c "import sys;[print('$'+hex(ord(ch))[2:]+',') for ch in input()]" > 期末試験.map
```


できた 期末試験.map は以下のようになっています。
```
$671f,
$672b,
$8a66,
$9a13,
$5bfe,
$7b56,
$672c,
$90e8,
$8a66,
$9a13,
$304b,
$3089,
$307e,
$3067,
$20,
$3042,
$3068,
$30,
$31,
$32,
$33,
$34,
$35,
$36,
$37,
$38,
$39,
$65e5,
$3068,
$6642,
$9593,
$5206,
$79d2,
```

"と" の文字コード $3068 がダブってますが、後の処理でダブリは除いて処理されるのでここで特に処理は必要ありません。

## c ファイルを作る
```
$ ../u8g2/tools/font/bdfconv/bdfconv -v -b 0 -f 1 -M 期末試験.map SeitenKaisho_32pt.bdf -o SeitenKaisho_32ptpart.c
```

としてできた `SeitenKaisho_32ptpart.c` をスケッチと同じ場所に移し、コンパイル設定で`SeitenKaisho_32ptpart.c` を加えます。

`SeitenKaisho_32ptpart.c` の最初の部分
```
const uint8_t bdf_font[1814] U8G2_FONT_SECTION("bdf_font") = 
```
を
```
#define LGFX_USE_V1
#include <LovyanGFX.hpp>

const uint8_t myFont_data[1814] = 
```
に、変更します。もし、`#ifdef U8G2_USE_LARGE_FONTS` という行があればそれも削除しておきます。

更に、末尾に
```
extern const lgfx::U8g2font myFont = { myFont_data };
```

を追加します。もし末尾に `#endif /* U8G2_USE_LARGE_FONTS */` という行があれば削除しておきます。

# スケッチ
```
#define LGFX_USE_V1
#include "arduinolib_for_PC.hpp"
#include <LovyanGFX.hpp>
#include <LGFX_AUTODETECT.hpp>
#include "Segment16C_Bold_Italic_24P.h"

#include <time.h> // for PC
static LGFX display ( 180,360,1 );
static LGFX_Sprite sprite1(&display);
static LGFX_Sprite sprite2(&display);
static LGFX_Sprite sprite3(&display);
extern const lgfx::U8g2font myFont;
struct tm target;
time_t present, targettime;
time_t remain;

void setup() {
  display.init();
  display.setSwapBytes(true); 
  display.fillRect(0,0,200,360,display.color888(0,60,10));
  sprite1.createSprite(33,270);
  sprite2.createSprite(37,360);
  sprite3.createSprite(33,225);
  sprite1.fillRect(0,0,100,320,display.color888(254,255,240));
  sprite1.setFont(&myFont);
  sprite1.setTextColor(display.color888(5,0,0));
  sprite1.println("期\n末\n試\n験\n対\n策\n本\n部");
  sprite1.pushSprite(130,20);
  // 2023/2/23 9:00 までの時間
  target.tm_year = 123;    // 2000年=100
  target.tm_mon = 1;       //  1月＝0 
  target.tm_mday = 23;
  target.tm_hour = 9;      
  target.tm_min = 0;       
  target.tm_sec = 0;       
  target.tm_isdst = -1;    // not summer time
}

void loop(){
  struct tm *newtime;
  time_t ltime;
  time(&ltime);
  targettime = mktime(&target);
  /* ターゲットまでの秒数 */
//  newtime = localtime(&ltime);
  if ( targettime < ltime ){
    remain = difftime( ltime,targettime);
  } else {
    remain = difftime(targettime, ltime);
  }
  newtime = gmtime(&remain);
   
  sprite3.fillRect(0,0,100,360,display.color888(254,255,240));
  sprite3.setFont(&myFont);
  sprite3.setTextColor(display.color888(5,0,0));

  sprite2.fillRect(0,0,100,360,display.color888(254,255,240));
  sprite2.setFont(&myFont);
  sprite2.setTextColor(display.color888(205,0,0));
  sprite2.setCursor(0,0);
  sprite2.printf("%02d\n日\nと\n%02d\n時\n間\n%02d\n分\n%02d\n秒",
        remain / (60*60*24),newtime->tm_hour, newtime->tm_min, newtime->tm_sec);
  sprite2.pushSprite(10,0);
  sprite3.setCursor(0,0);
  if ( targettime < ltime ){
    sprite3.println("試\n験\nか\nら");
  } else {
    sprite3.println("試\n験\nま\nで\n \nあ\nと");
  }
  sprite3.pushSprite(70,40);
  delay(1000);
}
```
2023/2/23 09:00 までの時間をカウントダウンします。試験の開始時間はハードコーディングしています。

なお、プログラム内部で時間はUTFで計算しています。
これは、
「UTC ベースで時間データを扱う(IoTプログラミング)」
https://qiita.com/nanbuwks/items/e3714f6b0b6b0b8585f4
で議論した IoT デバイスで TimeZone を扱う問題に則ったものです。

# リソース削減効果

先に作った `SeitenKaisho_32ptpart.c` 
```
const uint8_t bdf_font[336229]
```
に比べ、`SeitenKaisho_32pt.c` は
```
const uint8_t bdf_font[1814]
```
336KBytes が 1.8KBytes に下がったので劇的ですね!