👁️‍🗨️

【LvGL】フォントの使い方・カスタムフォントの導入

に公開

LvGLのフォントについてわかったことまとめ

夫れ日本人ならば、日本語が表示されるか否かについて気になるのは、当然のことである。将、デザインに趣向を凝らすならば、任意のフォントが使えるか否かについて注意を向けることもまた、当然のことであろう。

本記事では、LvGLのフォントについて試行した結果を紹介する。

フォントの使い方

https://docs.lvgl.io/7.11/overview/font.html#built-in-fonts

上はバージョン7.11の情報である。サイズが異なるだけのものも計数に含むと、概ね25種程のフォントが標準で用意されている。

しかし、初期状態では「default font」となっている唯一つのフォントを除き、全て無効になっているようである。

フォントを有効にする

フォントに関する設定の箇所

上に引用する通り、LV_FONT_MONTSERRAT_16のみ1とあり、それ以外の全てが0となっている。なおこのLV_FONT_MONTSERRAT_16は、「default font」に一致している。

default fontの記載

このような1または0の違いが意味するところは、次の箇所から分かる。

フォントの宣言箇所

下に引用した宣言の箇所は、このような記述となっている。

#if LV_FONT_⋯               // 条件分岐:「`LV_FONT_⋯`が`1`ならば」
LV_FONT_DECLARE(lv_font_⋯)  // `lv_font_⋯`として宣言され、フォントが有効になる
#endif                       // 条件分岐終了

ここで、LV_FONT_MONTSERRAT_16以外は全て0であるから、この条件分岐に該当せずLV_FONT_DECLAREによって宣言されないため、無効になるのである。

https://github.com/Seeed-Studio/Seeed_Arduino_LvGL/blob/master/src/src/lv_font/lv_font.h#L130-L220


参考:LV_FONT_DECLAREの定義

https://github.com/Seeed-Studio/Seeed_Arduino_LvGL/blob/master/src/src/lv_font/lv_font.h#L128

まとめると、このように云える。

LV_FONT_⋯の値 フォントの有効性
0 無効
1 有効

これを念頭に、使用したいフォントについて01に変更すればよい。

なお、この設定はlv_conf_internal.hというファイルに存在する。

参考:各ライブラリー毎の位置

何故フォントが無効になっているのか

以上に述べた通り設定を変更すれば済むとはいえ、そもそも何故設定を変更しなければ使えないようになっているのだろうか。

その答えは単純に、「フォントを使うだけ嵩張るから」であろうと考えられる。

LvGLが対象とする「組込みシステム」は、現代の家庭用PCに比較すると大きく性能が劣るため、あらゆる条件に制約が憑いて回る。

参考:性能比較

例にWio Terminalの性能を並べよう。今日日、ファイル一つ分のサイズとして見ることも珍しくない数値である。

項目 数値
Program Memory Size 512KB
External Flash 4MB
RAM Size 192KB

(引用:https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/#specification)

これと現代の家庭用PCの数値を一つ比較してみる。

DELLの記事によれば、

PCごとに、取り付けることができる最小メモリー容量と最大メモリー容量は決まっています。たとえば、特定のDell製デスクトップまたはノートパソコンは、最小2 GB、最大16 GBのメモリーをサポートする場合があります。

DELLPCの最小RAM2GBらしい。192KBと比較してみよう。

JavaScriptで計算
> let wio = 192;
< 192
> let dell = 2 * 1024 * 1024;
< 2097152
> dell / wio
< 10922.666666666666

大凡、一万倍もの格差があるとのこと。

更にWikipediaによれば、PC-9801RAM128KBであったという。これは1982年というから、2025年から遡ると半世紀弱も前の話である。

https://ja.wikipedia.org/wiki/PC-9800シリーズ

このように限られた環境下で運用するためには、最低限必要なもの(ここで言う「default font」)を用意した後は、無駄なものは含めないように気を配るのである。

検証:フォントを有効にしたことによる影響

実際にフォントを一つ有効にして、その前後を比較してみよう。下は、本記事で後述するカスタムフォント使用時の様子である。

Arduino IDE コンパイル後のメッセージ
最大507904バイトのフラッシュメモリのうち、スケッチが163288バイト(32%)を使っています。

ここで、LV_FONT_DEJAVU_16_PERSIAN_HEBREWを有効にするべく、設定を変更する。なお、もともと有効であったフォントは有効のままにしてある。

lv_conf_internal.h(抜粋)
#ifndef LV_FONT_DEJAVU_16_PERSIAN_HEBREW
// #define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0  /*Hebrew, Arabic, PErisan letters and all their forms*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 1  /*Hebrew, Arabic, PErisan letters and all their forms*/
#endif
Arduino IDE コンパイル後のメッセージ(フォント追加)
最大507904バイトのフラッシュメモリのうち、スケッチが202688バイト(39%)を使っています。

単純に差を取ると

202688 - 163288 = 39400

より、39400B(約38KB)増えたことが分かる。

若し使わないフォントを有効にしている場合は、念のため無効にすることを推奨する。

カスタムフォントの使い方

続いて、任意のフォントを使う手順について述べる。

ところで、既に「フォントは嵩張る」といった話をした通り、任意のフォントを使う際に容量への配慮を要することは明らかである。後述もするように、「フォントから必要な文字のみ抽出する」という工夫を迫られるだろう。

カスタムフォントの必要性

結論から言えば、(バージョン7.0.2の場合、)LvGL標準のフォントのみで日本語を表現することは難しい。

用意されたフォントは.cファイルとして存在しており、その中にはlv_font_simsun_16_cjk.cというファイルがある。このCJKとは「Chinese」、「Japanese」、「Korean」を指し、即ちこれらの圏で共通して用いられている文字、「漢字」を暗に示すことがある。

このlv_font_simsun_16_cjk.cを見ると、漢字がフォントのデータとして含まれている。ところが、仮名は全く存在しないのである。

漢文や書簡文、候文で良ければ、「日本語対応可」と言えるだろう。しかし書き下し文は表記できない。なにより、和歌を表現できないではないか。

日本語を扱う上で、用意されたフォントのみでは満足でない。而して、仮名を始め、必要に応じて任意のフォントを用意せねばならぬのである。

フォント入手から適用まで

ここからは、.ttfファイルのフォントをLvGLに適用するまでの手順を述べてゆく。

なお、一部手順はこの記事も参考にしている。

http://akirahitosi.blog.fc2.com/blog-entry-25.html?sp

1. .ttfファイルを入手する

本記事では.ttf型式のフォントを扱う。なお、手順の確認に重点を置くため、1字のみ収録する極めて軽量なフォントを使っている。

https://glyphwiki.org/wiki/u2ffa-ufa66-u2ff1-u2ff0-u660c-u232ad-u2ff0-u660d-u5dfe

リンク先の「1字フォント」とあるところから、フォントをダウンロードする。

はしだて
画像の右側に「1字フォント」とある

グリフウィキの1字フォントについて

1字フォント」は、文字コードの有無によらずグリフを表示できるフォントである。私の情報が遅れていなければ、このフォントが表す文字には、未だUnicodeは存在しないはずである。

このように汎用的な使途であるため、全ての「1字フォント」は特定の文字「〓(下駄)」に対応している。文字コードが存在していようとも、「1字フォント」は必ず「〓」に対応している。

2. .ttfファイルの名前を確認する

ダウンロードしたフォントの名前は、
u2ffa-ufa66-u2ff1-u2ff0-u660c-u232ad-u2ff0-u660d-u5dfe.ttf
というものになる。

この名前には次の問題が見て取れる。

  1. 半角ハイフン-が含まれている
  2. 長い

前者は致命的な問題であり、このまま.cファイルに変換するとエラーになる。半角アンダーバー_に置換するか、分かりやすい名前に変えることを忘れてはならない

後者は、Arduino IDEによって関知された場合に「長すぎる」とエラーになる。管轄外のフォルダーに移動すれば解消され、LvGLで用いる分には問題ないため今回は強行したが、短くした方がよい。

長すぎるファイル名

3. .ttfファイルを.cファイルに変換する

https://lvgl.io/tools/fontconverter

LvGL公式のOnline Font Converterを使って、.ttfファイルを.cファイルに変換することができる。当然乍ら、LvGLで用いるための型式に変換されるだけであり、C言語で記述されたあらゆるシステムに使えるものではない。

今回は次のように入力項目へ記載した。

項目 内容 備考
Name lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe フォントの名前
Size 30 単位はピクセル
Bpp 8 bit-per-pixel 標準のフォントの多くは4 bit-per-pixelである
Fallback lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe よくわからないのでNameと同じにした
Output format C file Binaryにすることもできる
チェックボックス 全部無視 拘る人は確り読めばよい
TTF/WOFF font u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.ttf ファイルを選択する
Range 0x3013 UnicodeU+3013であることによる
Symbols 1字フォント」は必ずに対応する

これらの内、最後のRange及びSymbolsは、選択したフォントの中から抽出する文字領域を指定する項目であり、極めて重要な意味を持つ。フォントにより収録する文字数は異なれど、収録される全ての文字を抽出することは現実的でない。

従って、システムで使用する文字を設計時点で洗い出して置き、それらに限定して抽出することが最も望ましい方法である。あるいは、LvGL標準のフォントに不足している仮名のみ抽出するといったように、文字種に限定した抽出が現実的であろう。

蛇足:日本語とUnicode

Unicodeに於ける仮名等日本語に関する文字の領域は、次の通り散り〴〵になっている。但しこれらは一部に過ぎない。

  • 記号・句読点
    U+3000( )・U+3001(、)・U+3002(。)・U+3005(々)
    この他、〆、括弧(〈〉・《》・「」・『』・【】⋯)、などがある

https://ja.wikipedia.org/wiki/CJKの記号及び句読点

  • 平仮名(捨て仮名・濁点付き含む)
    U+3041(ぁ) ~ U+3096(ゖ)
  • 平仮名(濁点・踊り字・合略仮名)
    U+3099( ゙) ~ U+309F(ゟ)
    U+3099U+309Aはダイアクリティカルマークのようになっているため、全角スペースと共に表示している。環境によって表示が崩れる場合があることに注意。
    (崩れそうな表示例:「り゙」「た゚」)
  • 片仮名(記号・捨て仮名・濁点付き・踊り字・合略仮名含む)
    U+30A0(゠) ~ U+30FF(ヿ)
  • 片仮名拡張
    U+31F0(ㇰ) ~ U+31FF(ㇿ)
  • 囲みCJK文字・月・CJK互換用文字
    ㋿、㌀、㍘、㏡など

https://ja.wikipedia.org/wiki/囲みCJK文字・月

https://ja.wikipedia.org/wiki/CJK互換用文字

  • 半角・全角形
    半角片仮名(ァ・ィ・ゥ⋯)など

https://ja.wikipedia.org/wiki/半角・全角形

  • 変体仮名
    U+1B000 ~ U+1B122
  • 小書き仮名拡張

https://ja.wikipedia.org/wiki/小書き仮名拡張

変換が終了すると、.cファイルがダウンロードされる。

4. .cファイルを一部修正する

変換された.cファイルの内容を一部修正する。
この手順は「Wio TerminalSeeed_Arduino_LvGLを用いた」が為に生じている可能性があるため、環境によって履行の是非を検討するべきである。

before
lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.c
/*******************************************************************************
 * Size: 30 px
 * Bpp: 8
 * Opts: --bpp 8 --size 30 --no-compress --font u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.ttf --symbols 〓 --range 12307 --format lvgl -o lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.c
 ******************************************************************************/

#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif};

extern const lv_font_t lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe;


/*-----------------
 *  PUBLIC FONT
 *----------------*/
after
lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.c
/*******************************************************************************
 * Size: 30 px
 * Bpp: 8
 * Opts: --bpp 8 --size 30 --no-compress --font u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.ttf --symbols 〓 --range 12307 --format lvgl -o lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.c
 ******************************************************************************/

/*
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
*/
#include <lvgl.h>};

// extern const lv_font_t lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe;


/*-----------------
 *  PUBLIC FONT
 *----------------*/

#include "lvgl/lvgl.h"が実行されてエラーが出たため、必ず#include <lvgl.h>となるようにした。

extern const lv_font_tは、フォントの宣言云云で触れたマクロLV_FONT_DECLAREに大きく関与している。

https://github.com/Seeed-Studio/Seeed_Arduino_LvGL/blob/master/src/src/lv_font/lv_font.h#L128

このマクロは、LV_FONT_DECLARE(font_name)と書いた箇所がextern lv_font_t font_nameと置換されるというものである。今回行う手順では、

LV_FONT_DECLARE(lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe)

として宣言する方法を取るため、これが置換されて

extern lv_font_t lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe

となり、重複する虞がある。よってフォントの.cファイル側では、extern const lv_font_tの記述を削除している。

5. .cファイルをフォントのフォルダーに追加する

.cファイルが格納されているフォルダーが存在するため、そこに同じくlv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.cを追加する。

参考:バージョン互換性のなさ

フォルダー名すら変わっている。

6. 設定変更

  • lv_font.mk
    フォルダーに追加した.cファイルの名前を記載する。
before
lv_font.mk(抜粋)
︙
CSRCS += lv_font_unscii_8.c
CSRCS += lv_font_dejavu_16_persian_hebrew.c

DEPPATH += --dep-path $(LVGL_DIR)/$(LVGL_DIR_NAME)/src/lv_font
︙
after
lv_font.mk(抜粋)
︙
CSRCS += lv_font_unscii_8.c
CSRCS += lv_font_dejavu_16_persian_hebrew.c
# custom font
# CSRCS += lv_font_glyphwiki_u3771_var_002.c # 別のカスタムフォントを試した跡
CSRCS += lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe.c

DEPPATH += --dep-path $(LVGL_DIR)/$(LVGL_DIR_NAME)/src/lv_font
︙
  • lv_font.h
    LV_FONT_DECLAREマクロでフォントを宣言し、有効にする。
before
lv_font.h(抜粋)
/*Declare the custom (user defined) fonts*/
#ifdef LV_FONT_CUSTOM_DECLARE
LV_FONT_CUSTOM_DECLARE
#endif
after
lv_font.h(抜粋)
/*Declare the custom (user defined) fonts*/
#ifdef LV_FONT_CUSTOM_DECLARE
// LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(lv_font_glyphwiki_u3771_var_002) // 別のカスタムフォントを試した跡
LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe)
#endif

以上の手順で、カスタムフォントを導入することができた。

カスタムフォント実装例

導入したフォントが正しく機能するか、実際にアプリケーションを作って動作確認する。本記事のプログラムはあくまでもWio Terminalでの実行を想定しているため、他の環境では動かない可能性がある。また、コードの整頓を優先してクラス化、抽象化している。

https://zenn.dev/amenaruya/articles/273566fb230507

プログラム
  • 抽象部
LvglArduinoAbstractLayer.hpp
#ifndef __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__
#define __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__

#include <Arduino.h>
#include <TFT_eSPI.h>
#include <lvgl.h>

template<class T>
class LAAL {
private:
    /* delete copy constructor */
    LAAL(const LAAL&) = delete;
    /* delete copy operator */
    LAAL& operator=(const LAAL&) = delete;

protected:
    /* singleton */
    static T*           __smpLaalSingleton;

    /* TFT LCD */
    static TFT_eSPI*    __mpTft;

    /* tick (1~10) */
    uint32_t            __mu32TickPeriod;

    /* ディスプレイ */

    /* display buffer */
    lv_disp_buf_t       __smDisplayBuffer;
    /* main color buffer */
    lv_color_t          __smColorBufferMain[LV_HOR_RES_MAX * 10];
    /* sub color buffer */
    lv_color_t          __smColorBufferSub[LV_HOR_RES_MAX * 10];
    /* display driver */
    lv_disp_drv_t       __mDisplayDriver;
    /* display */
    lv_disp_t*          __mpDisplay;
    /* 画面初期化 */
    static void         __mFDisplayFlush(
        lv_disp_drv_t*      pDisplayDriver,
        const lv_area_t*    pArea,
        lv_color_t*         pColor
    );
    /* 画面設定 */
    virtual void        __mFDisplaySetup();

    /* 入力デバイス(encode) */

    /* input device driver */
    lv_indev_drv_t      __mInputDeviceDriverEncoder;
    /* input device */
    lv_indev_t*         __mpInputDeviceEncoder;
    /* Reading encoder callback */
    static bool         __mFbReadEncoder(
        lv_indev_drv_t*     pInputDeviceDriver,
        lv_indev_data_t*    pInputDevicesData
    );
    /* デバイス設定 */
    virtual void        __mFDeviceSetup();

    /* 画面要素 */

    /* widget */
    /*
    example:
    lv_obj_t*           __mpObject;
    void                __mFObjectEventhandler(
        lv_obj_t*           pObject,
        lv_event_t          event
    ) = 0;
    */

    /* group */
    /*
    example:
    lv_group_t*         __mpGroup;
    void                __mFGroupSetup();
    */

    /* 画面構成 */
    virtual void        __mFContentsSetup() = 0;

    /* constructor */
    void                __mFNew();
    LAAL() = default;
    /* destructor */
    virtual ~LAAL() = default;

public:
    /* 設定 */
    void                mFSetup();
    /* 画面実行 */
    void                mFOperateGraphic();
};

#include "LvglArduinoAbstractLayer.ipp"

#endif // __LVGL_ARDUINO_ABSTRACT_LAYER_HPP__

LvglArduinoAbstractLayer.ipp
template<class T>
T* LAAL<T>::__smpLaalSingleton = nullptr;
template<class T>
TFT_eSPI* LAAL<T>::__mpTft = nullptr;

template<class T>
void LAAL<T>::__mFNew() {
    /* lvglの初期化 */
    lv_init();

    /* TFTの初期設定 */
    (this -> __mpTft) -> begin();
    (this -> __mpTft) -> setRotation(3);
}

template<class T>
void LAAL<T>::mFSetup() {
    /* 画面設定 */
    (this -> __mFDisplaySetup)();
    /* 入力設定 */
    (this -> __mFDeviceSetup)();
    /* 画面作成 */
    (this -> __mFContentsSetup)();
}

template<class T>
void LAAL<T>::__mFDisplayFlush(
    lv_disp_drv_t*      pDisplayDriver,
    const lv_area_t*    pArea,
    lv_color_t*         pColor
) {
    const uint32_t w = pArea -> x2 - pArea -> x1 + 1;
    const uint32_t h = pArea -> y2 - pArea -> y1 + 1;

    LAAL<T>::__mpTft -> startWrite();
    LAAL<T>::__mpTft -> setAddrWindow(pArea -> x1, pArea -> y1, w, h);
    LAAL<T>::__mpTft -> pushColors(&pColor -> full, w * h, true);
    LAAL<T>::__mpTft -> endWrite();

    lv_disp_flush_ready(pDisplayDriver);
}

template<class T>
void LAAL<T>::mFOperateGraphic() {
    while (1) {
        /* operate tasks */
        this -> __mu32TickPeriod = lv_task_handler();
        /* delay */
        delay(this -> __mu32TickPeriod);
        /* tell lvgl system elapsed time */
        lv_tick_inc(this -> __mu32TickPeriod);
    }
}

template<class T>
void LAAL<T>::__mFDisplaySetup() {
    /* 画面表示用bufferそのものの初期化 */
    lv_disp_buf_init(
        &(this -> __smDisplayBuffer),
        this -> __smColorBufferMain,
        this -> __smColorBufferSub,
        LV_HOR_RES_MAX * 10
    );

    /* 画面制御設定 */
    lv_disp_drv_init(&(this -> __mDisplayDriver));
    (this -> __mDisplayDriver).ver_res = LV_VER_RES_MAX;
    (this -> __mDisplayDriver).flush_cb = this -> __mFDisplayFlush;
    (this -> __mDisplayDriver).buffer = &(this -> __smDisplayBuffer);
    this -> __mpDisplay = lv_disp_drv_register(&(this -> __mDisplayDriver));
}

template<class T>
bool LAAL<T>::__mFbReadEncoder(
    lv_indev_drv_t*     pInputDeviceDriver,
    lv_indev_data_t*    pInputDevicesData
) {
    (void) pInputDeviceDriver;
    (void) pInputDevicesData;
    return false;
}

template<class T>
void LAAL<T>::__mFDeviceSetup() {
    /* 入力の設定 */
    lv_indev_drv_init(&(this -> __mInputDeviceDriverEncoder));
    (this -> __mInputDeviceDriverEncoder).type = LV_INDEV_TYPE_ENCODER;
    (this -> __mInputDeviceDriverEncoder).read_cb = this -> __mFbReadEncoder;
    this -> __mpInputDeviceEncoder = lv_indev_drv_register(&(this -> __mInputDeviceDriverEncoder));
}

  • アプリケーション部
SimpleFontPlayGround.hpp
#ifndef __SIMPLE_FONT_PLAY_GROUND_HPP__
#define __SIMPLE_FONT_PLAY_GROUND_HPP__

#include "LvglArduinoAbstractLayer.hpp"

class App final: public LAAL<App> {
private:
    /* 画面設定 */
    void                    __mFDisplaySetup();

    // /* last tick */
    // static uint32_t         __mu32LastTick;
    /* Reading encoder callback */
    static bool             __mFbReadEncoder(
        lv_indev_drv_t*     pInputDeviceDriver,
        lv_indev_data_t*    pInputDevicesData
    );
    /* デバイス設定 */
    void                    __mFDeviceSetup();

    /* position */

    /* array */
    const lv_point_t        __maPosition[3] = {{0,0}, {0, 1}, {1,1}};

    /* tileview */
    static lv_obj_t*        __mpObjectTileview;
    /* tile */
    static lv_obj_t*        __mpObjectTile;
    /* label */
    static lv_obj_t*        __mpObjectLabel;
    /* tileview配置 */
    void                    __mFTileviewSetup();

    // /* group */
    // static lv_group_t*      __mpGroup;
    // /* group設定 */
    // void                    __mFGroupSetup();

    /* 画面構成 */
    void                    __mFContentsSetup();

    /* constructor */
    App();
    /* destructor */
    ~App();

public:
    /* 初期化 */
    static App*             mFpLaalInitialize(
        TFT_eSPI*           pTft
    );
};

#endif // __SIMPLE_FONT_PLAY_GROUND_HPP__

SimpleFontPlayGround.cpp
#include "SimpleFontPlayGround.hpp"

/* static member variable */

// uint32_t    App::__mu32LastTick = 0;
lv_obj_t*   App::__mpObjectTileview = nullptr;
lv_obj_t*   App::__mpObjectTile = nullptr;
lv_obj_t*   App::__mpObjectLabel = nullptr;
// lv_group_t* App::__mpGroup = nullptr;

/* member function */

/* initialize */

/*static*/ App* App::mFpLaalInitialize(
    TFT_eSPI*           pTft
) {
    /* 未定ならば初期化 */
    if (LAAL<App>::__smpLaalSingleton == nullptr) {
        /* static member variableの初期化 */
        LAAL<App>::__mpTft = pTft;
        /* 子クラスの初期化 */
        LAAL<App>::__smpLaalSingleton = new App();
    }
    /* 既初期化の場合でも既存インスタンスへのポインターが返却される */
    return LAAL<App>::__smpLaalSingleton;
}

App::App() {
    LAAL<App>::__mFNew();
}
App::~App() {}

/* display */

void App::__mFDisplaySetup() {
    LAAL<App>::__mFDisplaySetup();
}

/* input */

/*static*/ bool App::__mFbReadEncoder(
    lv_indev_drv_t*     pInputDeviceDriver,
    lv_indev_data_t*    pInputDevicesData
) {
    (void) pInputDeviceDriver;
    (void) pInputDevicesData;

    // /* 経過時間 */
    // uint32_t u32TickElapsed = lv_tick_get() - App::__mu32LastTick;
    // /* tick更新 */
    // bool bUpdateTick = false;

    // /* 押下如何 */
    // if (digitalRead(WIO_5S_PRESS) == LOW) {
    //     /* send pressed */
    //     lv_group_send_data(App::__mpGroup, LV_KEY_ENTER);

    //     /* set state pressed */
    //     pInputDevicesData -> state = LV_INDEV_STATE_PR;

    //     /* update */
    //     bUpdateTick = true;
    // }
    // else {
    //     /* set state relesed */
    //     pInputDevicesData -> state = LV_INDEV_STATE_REL;
    // }

    // /* 左 */
    // if (digitalRead(WIO_5S_LEFT) == LOW && u32TickElapsed > 250) {
    //     /* send left */
    //     lv_group_send_data(App::__mpGroup, LV_KEY_LEFT);

    //     /* update */
    //     bUpdateTick = true;
    // }
    // /* 右 */
    // else if (digitalRead(WIO_5S_RIGHT) == LOW && u32TickElapsed > 250) {
    //     /* send right */
    //     lv_group_send_data(App::__mpGroup, LV_KEY_RIGHT);

    //     /* update */
    //     bUpdateTick = true;
    // }

    // /* 上 */
    // if (digitalRead(WIO_5S_UP) == LOW && u32TickElapsed > 250) {
    //     /* send up */
    //     lv_group_send_data(App::__mpGroup, LV_KEY_UP);
    //     /* update */
    //     bUpdateTick = true;
    // }
    // /* 下 */
    // else if (digitalRead(WIO_5S_DOWN) == LOW && u32TickElapsed > 250) {
    //     /* send down */
    //     lv_group_send_data(App::__mpGroup, LV_KEY_DOWN);
    //     /* update */
    //     bUpdateTick = true;
    // }

    // /* B (中) */
    // if (digitalRead(WIO_KEY_B) == LOW && u32TickElapsed > 250) {
    //     /* focus to  */
    //     lv_group_focus_obj(App::__);

    //     /* update */
    //     bUpdateTick = true;
    // }

    // /* A (右) */
    // if (digitalRead(WIO_KEY_A) == LOW && u32TickElapsed > 250) {
    //     /* positive difference (focus) */
    //     pInputDevicesData -> enc_diff = 1;

    //     /* update */
    //     bUpdateTick = true;
    // }
    // /* C (左) */
    // else if (digitalRead(WIO_KEY_C) == LOW && u32TickElapsed > 250) {
    //     /* negative difference (focus) */
    //     pInputDevicesData -> enc_diff = -1;

    //     /* update */
    //     bUpdateTick = true;
    // }
    // else {
    //     /* zero defference */
    //     pInputDevicesData -> enc_diff = 0;
    // }

    // if (bUpdateTick) {
    //     /* update tick */
    //     App::__mu32LastTick = lv_tick_get();
    // }

    /* no data buffered */
    return false;
}

void App::__mFDeviceSetup() {
    /* 入力の設定 */
    lv_indev_drv_init(&(LAAL<App>::__mInputDeviceDriverEncoder));
    (LAAL<App>::__mInputDeviceDriverEncoder).type = LV_INDEV_TYPE_ENCODER;
    (LAAL<App>::__mInputDeviceDriverEncoder).read_cb = App::__mFbReadEncoder;
    LAAL<App>::__mpInputDeviceEncoder = lv_indev_drv_register(&(LAAL<App>::__mInputDeviceDriverEncoder));

    // /* enable 5way switch */
    // pinMode(WIO_5S_UP, INPUT);
    // pinMode(WIO_5S_DOWN, INPUT);
    // pinMode(WIO_5S_LEFT, INPUT);
    // pinMode(WIO_5S_RIGHT, INPUT);
    // pinMode(WIO_5S_PRESS, INPUT);

    // /* enable 3 buttons */
    // pinMode(WIO_KEY_A, INPUT);
    // pinMode(WIO_KEY_B, INPUT);
    // pinMode(WIO_KEY_C, INPUT);
}

/* contents */

void App::__mFContentsSetup() {
    (this -> __mFTileviewSetup)();
    // (this -> __mFGroupSetup)();
}

/* callback */

/* widget */

void App::__mFTileviewSetup() {
    /* create a tileview */
    this -> __mpObjectTileview = lv_tileview_create(lv_scr_act(), NULL);
    /* set valid positions */
    lv_tileview_set_valid_positions(this -> __mpObjectTileview, this -> __maPosition, 3);
    /* set edge flash */
    lv_tileview_set_edge_flash(this -> __mpObjectTileview, true);

    /* create a tile */
    this -> __mpObjectTile = lv_obj_create(this -> __mpObjectTileview, NULL);
    /* set size */
    lv_obj_set_size(this -> __mpObjectTile, LV_HOR_RES, LV_VER_RES);
    /* add this tile to the tileview */
    lv_tileview_add_element(this -> __mpObjectTileview, this -> __mpObjectTile);

    /* create a label */
    this -> __mpObjectLabel = lv_label_create(this -> __mpObjectTile, NULL);
    /* set the label center of the tile */
    lv_obj_align(this -> __mpObjectLabel, NULL, LV_ALIGN_CENTER, 0, 0);
    /* set text */
    // lv_label_set_text(this -> __mpObjectLabel, "A");
    // lv_label_set_text(this -> __mpObjectLabel, "ת");
    lv_label_set_text(this -> __mpObjectLabel, "〓");
    /* set text font */
    lv_obj_set_style_local_text_font(
        this -> __mpObjectLabel,
        LV_LABEL_PART_MAIN,
        LV_STATE_DEFAULT,
        &lv_font_glyphwiki_u2ffa_ufa66_u2ff1_u2ff0_u660c_u232ad_u2ff0_u660d_u5dfe
        // &lv_font_dejavu_16_persian_hebrew
    );
}

/* group */

// void App::__mFGroupSetup() {
//     /* create group */
//     this -> __mpGroup = lv_group_create();
//     /* set the input device to the group */
//     lv_indev_set_group(LAAL<App>::__mpInputDeviceEncoder, this -> __mpGroup);

//     /* add the objects to the group */

//     /* focus to tileview */
// }

  • .inoファイル
#include <TFT_eSPI.h>
#include <lvgl.h>

#include "SimpleFontPlayGround.hpp"

/* TFT LCD */
static TFT_eSPI TFT;

/* App */
App* pApp;

void setup() {
    pApp = App::mFpLaalInitialize(&TFT);
    pApp -> mFSetup();
}

void loop() {
    pApp -> mFOperateGraphic();
}

実行結果

実行結果

文字の小ささと複雑さが相俟ってはっきり見えないが、文字コードの存在しない文字を画面に表示させることができている。このことから、フォントが正しく導入され、機能していることが確かめられる。

本記事では簡単な例に留めたため、より実践的な場面ではこの通り進まず彳亍することもあるだろう。目下の課題は、仮名を表示させることとなる。

Discussion