はじめに
Node-RED MCU EditionのDashboardで日本語を使用すると「???」で表示されます。
ふと、Moddableのブログ記事を見ていたら、Moddable自体は日本語を表示する機能が実装されていることを知りました。
仕組み
Moddableでフォントを扱う仕組みについて調べました。
また、DashboardはPiuを使用しているので、Piuでフォントを扱う仕組みを調べました。
BMFont(BitMap Font)が採用されていることまでわかりましたが、使い方がピンと来なかったので、次に実際の使用例を調べました。
前例(実装例)
スタックチャンの生みの親の石川さんがModdableのTwitterクライアントをサンプルで作られており、日本語が表示されていることを確認しました。
そのままではビルドが失敗し、起動後もメモリ不足のエラーで動作しなかったので、コードを一部修正しました。
$ 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
$ diff twitter.js twitter.js_org
2a3
> import secret from 'secret-token'
5c6
< const BEARER_TOKEN = ''
---
> const BEARER_TOKEN = secret.BEARER_TOKEN
仕組み
assets/fontsフォルダにフォントファイル(.fnt、.png)が置かれています。
├── 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で指定しています。
"resources": {
"*": ["./assets/icons/*", "./assets/images/*"],
"*-alpha": ["./assets/fonts/*"]
main.jsとtweet-application.jsでfontを指定しています。
font: 'Cica-Regular'
font: 'Cica-Bold'
これでようやく使い方がわかりました。
Dashboardの実装調査
assetsフォルダにフォントファイル(*.fnt, *.png)が置かれています。
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で指定しています。
"resources":{
"*": [
"./assets/ui_colour_picker"
],
"*-mask": [
"./assets/Roboto-Medium-12",
"./assets/Roboto-Medium-18",
"./assets/Roboto-Regular-18",
ui_templates.jsでfontを指定しています。
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で使用しているフォントファイルには日本語が含まれていません。
フォントファイルの作成
Twitterクライアントで使用しているフォントファイルを流用してNode-RED MCU EditionのDashboardで日本語表示できることを確認しましたが、フォントファイルを作ってみます。
Windowsの場合
Bitmap Font Generator、一択です。GUIアプリケーションでフォントファイルを作成できます。
macOSの場合
Glyph Designerが紹介されていますが、有償となるため、fontbmツールを使用しました。
macOSとLinuxの場合 (無償のfontbmツールを使用する場合)
fontbmツールはWindowsとLinuxはバイナリファイル(実行形式)が提供されていますが、macOSの場合はソースコードからビルドする必要があります。
ビルド手順
$ brew install freetype
$ git clone https://github.com/vladimirgamalyan/fontbm.git
$ cd fontbm
$ cmake .
$ make
フォントの準備
Noto Sans日本語フォントをダウンロードします。
zipファイルを展開し、staticフォルダの中のNotoSansJP-Regular.ttfを使用します。
BMFontファイルの作成
NotoSansJP-Regular.ttfをfontbmフォルダに置きます。
BMFontファイルを作成する場合、文字コードのブロック単位で指定するか、文字単位で指定するか選択できます。
ブロック単位で指定すると日本語以外の文字(例えば、中国語の漢字など)が含まれてしまうため、文字単位で指定してBMFontファイルを作成することにしました。
こちらのサイトでShift JISに含まれる文字を一覧化したファイルが公開されていましたので、こちらを使用することにしました。
$ 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)が表示されますが、気にしなくて大丈夫です。
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へ変更します。
$ mv Noto-Regular_0.png Noto-Regular.png
参考
.fntファイルはバイナリ形式のため、内容を確認するにはプログラムを作成します。
$ npm init -y
$ npm i fs
$ npm i parse-bmfont-binary
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を実行します。
$ 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に置きました。
結果
シミュレータも実機も問題なく表示されることを確認しました。
応用
MQTTでメッセージを送れば、デバイス側でサイネージ的に表示できます。
さいごに
これでようやくDashboardを実用的に使えるようになりました!