Node-RED MCU EditionのDashboardで日本語を表示できるようにしてみた

# はじめに

Node-RED MCU EditionのDashboardで日本語を使用すると「???」で表示されます。

![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/40ce9653-fbc7-10fa-2be4-0aea27e49cd8.png)

ふと、Moddableのブログ記事を見ていたら、Moddable自体は日本語を表示する機能が実装されていることを知りました。

https://www.moddable.com/blog/fonts/

# 仕組み

Moddableでフォントを扱う仕組みについて調べました。

https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/commodetto/Creating%20fonts%20for%20Moddable%20applications.md

また、DashboardはPiuを使用しているので、Piuでフォントを扱う仕組みを調べました。

https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/piu/piu.md

BMFont(BitMap Font)が採用されていることまでわかりましたが、使い方がピンと来なかったので、次に実際の使用例を調べました。

# 前例(実装例)

ｽﾀｯｸﾁｬﾝの生みの親の<A HREF="https://twitter.com/stack_chan">石川さん</A>がModdableのTwitterクライアントをサンプルで作られており、日本語が表示されていることを確認しました。

https://www.ecma-international.org/wp-content/uploads/Shinya-Ishikawa-Making-things-with-Moddable-and-ECMAScript-from-a-web-developers-perspective.pdf

https://github.com/meganetaaan/moddable-examples/tree/master/twitter

そのままではビルドが失敗し、起動後もメモリ不足のエラーで動作しなかったので、コードを一部修正しました。

```bash:manifest.json
$ diff manifest.json manifest.json_org

11a12
>       "./secret-token",
34,46c35,36
<     "static": 0,
<     "chunk": {
<         "initial": 65536,
<         "incremental": 0
<     },
<     "heap": {
<         "initial": 4096,
<         "incremental": 0
<     },
<     "keys": {
<         "available": 128
<     },
<     "stack": 600
---
>     "stack": 512,
>     "static": 131072
```

```bash:twitter.js
$ diff twitter.js twitter.js_org 

2a3
> import secret from 'secret-token'
5c6
< const BEARER_TOKEN = ''
---
> const BEARER_TOKEN = secret.BEARER_TOKEN
```

![IMG_4763.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/1904f848-cee8-c7fd-44db-33679369a0f9.jpeg)

## 仕組み

assets/fontsフォルダにフォントファイル(*.fnt、*.png)が置かれています。

```bash
├── assets
│   ├── fonts
│   │   ├── Cica-Bold.fnt
│   │   ├── Cica-Bold.png
│   │   ├── Cica-Regular.fnt
│   │   └── Cica-Regular.png
│   ├── icons
│   │   ├── comment_18x18.png
│   │   ├── favorite_18x18.png
│   │   ├── retweet_18x18.png
│   │   └── search_36x36.png
│   └── images
│       └── m5stack.png
├── cert
│   ├── digicerthasca.der
│   └── digicertssca.der
├── main.js
├── manifest.json
├── test.json
├── tweet-application.js
├── tweets.js
└── twitter.js
```

manifest.jsonのresourcesで指定しています。

```json
  "resources": {
    "*": ["./assets/icons/*", "./assets/images/*"],
    "*-alpha": ["./assets/fonts/*"]
```

main.jsとtweet-application.jsでfontを指定しています。

```javascript
font: 'Cica-Regular'
font: 'Cica-Bold'
```

これでようやく使い方がわかりました。

# Dashboardの実装調査

assetsフォルダにフォントファイル(*.fnt, *.png)が置かれています。

```bash
node-red-mcu/nodes/dashboard
├── ScrollerBehaviors.js
├── assets
│   ├── Roboto-Medium-12.fnt
│   ├── Roboto-Medium-12.png
│   ├── Roboto-Medium-18.fnt
│   ├── Roboto-Medium-18.png
│   ├── Roboto-Regular-18.fnt
│   ├── Roboto-Regular-18.png
│   ├── button.png
│   ├── glyphs.png
│   ├── popup.png
│   ├── slider.png
│   ├── switch.png
│   ├── ui_colour_picker.png
│   └── ui_colour_picker_mask.png
├── manifest.json
├── ui_chart.js
├── ui_colour_picker.js
├── ui_gauge.js
├── ui_nodes.js
├── ui_templates.js
└── ui_text_input.js
```

manifest.jsonのresourcesで指定しています。

```json
        "resources":{
                "*": [
                        "./assets/ui_colour_picker"
                ],
                "*-mask": [
                        "./assets/Roboto-Medium-12",
                        "./assets/Roboto-Medium-18",
                        "./assets/Roboto-Regular-18",
```

ui_templates.jsでfontを指定しています。

```javascript
			chartNoData: new Style({ font:"18px Roboto", color:halfGray, horizontal:"center" }),
			chartX: new Style({ font:"medium 12px Roboto", color:widgetText, horizontal:"center" }),
			chartY: new Style({ font:"medium 12px Roboto", color:widgetText, horizontal:"right" }),
			notification: new Style({ font:"18px Roboto", color:widgetText, horizontal:"left", left:10, right:10, top:10, bottom:10 }),
			keyboard: new Style({ font:"18px Roboto", color:BLACK }),
			textName: new Style({ font:"18px Roboto", color:widgetText, left:5, right:5 }),
			textValue: new Style({ font:"medium 18px Roboto", color:widgetText, left:5, right:5 }),
			textNameLeft: new Style({ font:"18px Roboto", color:widgetText, horizontal:"left", left:10 }),
			textValueLeft: new Style({ font:"medium 18px Roboto", color:widgetText, horizontal:"left", left:10 }),
			textNameRight: new Style({ font:"18px Roboto", color:widgetText, horizontal:"right", right:10 }),
			textValueRight: new Style({ font:"medium 18px Roboto", color:widgetText, horizontal:"right", right:10 }),
			textField: new Style({ font:"medium 18px Roboto", color:[TRANSPARENT,widgetText,widgetText,widget], horizontal:"left", left:10 }),
	result.styles.title = new Style({ font:"medium 18px Roboto", color:WHITE, horizontal:"left" });
	result.styles.titleMenuItem = new Style({ font:"medium 18px Roboto", color:[halfGray,groupText,groupText,WHITE], horizontal:"left" });
	result.styles.group = new Style({ font:"medium 18px Roboto", color:[groupText,groupText,groupText,groupText], horizontal:"left" });
	result.styles.button = new Style({ font:"medium 18px Roboto", color:[halfGray,WHITE,WHITE,WHITE] });
	result.styles.dropDown = new Style({ font:"medium 18px Roboto", color:[halfGray,widgetText,widgetText,WHITE], horizontal:"left", left:10 });
	result.styles.dropDownMenuItem = new Style({ font:"medium 18px Roboto", color:[halfGray,widgetText,widgetText,WHITE], horizontal:"left", left:10 });
			container.first.style = new Style({ font:"medium 18px Roboto", color:[REDTheme.colors.halfGray,color,color,color] });
```

これで修正すべき場所がわかりました。

# フォントファイルの比較

フォントファイルの中身を見ると明らかですが、Twitterクライアントで使用しているフォントファイルには日本語が含まれていますが、Node-RED MCU EditionのDashboardで使用しているフォントファイルには日本語が含まれていません。

![Cica-Regular.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/3c31abaf-4533-550a-b0a2-3e0f61794260.png)

![Roboto-Regular-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/c8209a9a-034b-e592-f8c1-b25cd42f9a72.png)

# フォントファイルの作成

Twitterクライアントで使用しているフォントファイルを流用してNode-RED MCU EditionのDashboardで日本語表示できることを確認しましたが、フォントファイルを作ってみます。

## Windowsの場合

Bitmap Font Generator、一択です。GUIアプリケーションでフォントファイルを作成できます。

http://www.angelcode.com/products/bmfont/

## macOSの場合

Glyph Designerが紹介されていますが、有償となるため、fontbmツールを使用しました。

https://71squared.com/glyphdesigner

## macOSとLinuxの場合 (無償のfontbmツールを使用する場合)

fontbmツールはWindowsとLinuxはバイナリファイル(実行形式)が提供されていますが、macOSの場合はソースコードからビルドする必要があります。

https://github.com/vladimirgamalyan/fontbm

### ビルド手順

```bash
$ brew install freetype
$ git clone https://github.com/vladimirgamalyan/fontbm.git
$ cd fontbm
$ cmake .
$ make
```

### フォントの準備

Noto Sans日本語フォントをダウンロードします。

https://fonts.google.com/noto/specimen/Noto+Sans+JP

zipファイルを展開し、staticフォルダの中のNotoSansJP-Regular.ttfを使用します。

### BMFontファイルの作成

NotoSansJP-Regular.ttfをfontbmフォルダに置きます。
BMFontファイルを作成する場合、文字コードのブロック単位で指定するか、文字単位で指定するか選択できます。
ブロック単位で指定すると日本語以外の文字(例えば、中国語の漢字など)が含まれてしまうため、文字単位で指定してBMFontファイルを作成することにしました。

こちらのサイトでShift JISに含まれる文字を一覧化したファイルが公開されていましたので、こちらを使用することにしました。

https://nakamura001.hatenablog.com/entry/20120910/1347241168

https://raw.github.com/nakamura001/JIS_CharacterSet/master/SHIFTJIS_custom/SHIFTJIS_custom_win_bom_utf8.txt

```bash
$ wget https://raw.github.com/nakamura001/JIS_CharacterSet/master/SHIFTJIS_custom/SHIFTJIS_custom_win_bom_utf8.txt
$ ./fontbm --font-file NotoSansJP-Regular.ttf --data-format bin --color 0,0,0 --font-size 18 --chars-file SHIFTJIS_custom_win_bom_utf8.txt --kerning-pairs regular --output Noto-Regular
```

警告(warning)が表示されますが、気にしなくて大丈夫です。

```bash
warning: glyph 8786 not found.
warning: glyph 65279 not found (it looks like Unicode byte order mark (BOM)).
```

コマンド実行が終了すると、Noto-Regular.fntとNoto-Regular_0.pngファイルが作成されます。
Noto-Regular_0.pngのファイル名はNoto-Regular.pngへ変更します。

```bash
$ mv Noto-Regular_0.png　Noto-Regular.png
```

### 参考

.fntファイルはバイナリ形式のため、内容を確認するにはプログラムを作成します。

```bash
$ npm init -y
$ npm i fs
$ npm i parse-bmfont-binary
```

font.jsファイルを作成します。

```javascript: font.js
var fs = require('fs')
var parse = require('parse-bmfont-binary')

fs.readFile('Noto-Regular.fnt', function(err, data) {
  if (err) throw err
  var font = parse(data)
  
  //do something with your font
  console.log(font)
})
```

font.jsを実行します。

```bash
$ node font.js

(省略)
      xadvance: 11,
      page: 0,
      chnl: 15
    },
    ... 6936 more items
  ],
  info: {
    size: -18,
    smooth: 0,
    unicode: 0,
    italic: 0,
    bold: 0,
    charset: '',
    stretchH: 100,
    aa: 1,
    padding: [ 0, 0, 0, 0 ],
    spacing: [ 0, 0 ],
    outline: 0,
    face: 'Noto Sans JP'
  },
  common: {
    lineHeight: 26,
    base: 21,
    scaleW: 2048,
    scaleH: 1024,
    pages: 1,
    packed: 0,
    alphaChnl: 0,
    redChnl: 4,
    greenChnl: 4,
    blueChnl: 4
  },
  pages: [ 'Noto-Regular_0.png' ]
}
```

# Dashboardの日本語化

node-red-mcuのdashboardノードのassetsフォルダにNoto-Regular.fntとNoto-Regular.pngファイルを置きます。
また、manifest.jsonとui_templates.jsを変更します。

```
node-red-mcu/nodes/dashboard
├── ScrollerBehaviors.js
├── assets
│   ├── Noto-Regular.fnt        (←追加)
│   ├── Noto-Regular.png        (←追加)
│   ├── Roboto-Medium-12.fnt
│   ├── Roboto-Medium-12.png
│   ├── Roboto-Medium-18.fnt
│   ├── Roboto-Medium-18.png
│   ├── Roboto-Regular-18.fnt
│   ├── Roboto-Regular-18.png
│   ├── button.png
│   ├── glyphs.png
│   ├── popup.png
│   ├── slider.png
│   ├── switch.png
│   ├── ui_colour_picker.png
│   └── ui_colour_picker_mask.png
├── manifest.json              (←変更)
├── ui_chart.js
├── ui_colour_picker.js
├── ui_gauge.js
├── ui_nodes.js
├── ui_templates.js            (←変更)
└── ui_text_input.js
```

フォントファイルと変更したファイル(manifest.json、ui_templates.js)はGitHubに置きました。

https://github.com/kitazaki/node-red-mcu-dashboard-japanese?tab=readme-ov-file

# 結果

シミュレータも実機も問題なく表示されることを確認しました。

![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/2826b12c-6289-294d-ae30-ce6d6527809f.png)

![GHA2_wgasAAZ_Ud.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/3743b865-5081-3972-40c3-524b99af4596.jpeg)

# 応用

MQTTでメッセージを送れば、デバイス側でサイネージ的に表示できます。

![GHBQeljbEAAgTwx.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/61993d12-506c-8fa3-e5db-38860ee29722.jpeg)

![IMG_4766.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/45b3cd0b-8b80-3f59-122f-e4cabec4c728.jpeg)

# さいごに

これでようやくDashboardを実用的に使えるようになりました！