Arduinoで遊ぶページ
Arduinoで遊んだ結果を残すページです
TrueTypeフォント
ESP32

概要

Arduino core for the ESP32とSDカード、TFTタッチシールドを使った、TrueTypeフォントを表示する実験です。

TrueTypeのことはほぼ何もわかっていないので、変なことをしている可能性が高いです。間違い等を見つけた方はお知らせください。連絡先はこちら

天気予報をTFTタッチシールドに表示する際に、日本語フォントが使えたら便利だなと思い、どうせなら、TrueTypeフォントが表示できないと考え、いろいろ調べながら試してみた結果です。

とりあえず、以下のことができるようになりました。

  • TrueTypeファイルの読み出し
    • TrueTypeフォント内のテーブルディレクトリの読み出し
    • HeadTableの読み出し
    • Cmap Format 4テーブルの読み出し
    • Simple Glyphの読み出し
  • Glyphの表示
    • 読みだしたGlyphのTFTタッチシールドへの表示 (instructions未使用)
    • Glyphの塗りつぶし

まだできていないこと、わからないこともたくさんあります。

  • Compound Glyphの読み出し
  • Adafruit_ILI9341(TFTタッチシールド用クラス)への組み込み
  • サイズを小さくするとつぶれてしまう
  • 空白文字の扱い
  • 高速化(仕様に従って数バイトずつ読み込みをしていたりするので、とても遅いです)
  • その他たくさん

文字を表示した様子。日本語は縦64ドット、アルファベットは縦48ドットです。画面の右側の白いやつは映り込みです…

フォントは、IPAが配布している、IPAフォント(ipag.ttf(ゴシック)とipam.ttf(明朝))です。それっぽく表示されているのではないかと思います。

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

実験

以下のサイトを参考にしながら、truetypeクラスなどの実装を行いました。SDカードに、TrueTypeフォントを入れて、読み込むようにしています。かなりメモリを使うと思うので、Arduino Unoでの動作は難しいと思います。truetypeクラスは、ファイルを読み出すだけのクラスです。画面への表示は実装していません。こちらは、GitHubに置きました。

URL 内容
TrueType™ Reference Manual TrueTypeのリファレンスマニュアルです。
TrueTypeのフォーマットを調べる TrueTypeのフォーマットを調査した結果が詳細に記載されています。このページを先頭に、かなりの記事が書かれています。
ブレゼンハムのアルゴリズム 直線を描画するためのアルゴリズムです。
To check if a point is inside a polygon ある点がポリゴンの中にあるのかを判定するアルゴリズム/プログラムです。
Let's read a Truetype font file from scratch JavaScriptを使って、TrueTypeフォントの構造を解説しているページで

以下は、truetypeクラスを利用して、TFTタッチシールドに文字を表示するためのサンプルプログラム(とてもかっこ悪い)です。

ESP-WROOM-32とTFTグラフィック液晶との接続は、TFTタッチシールドの実験を参照してください。

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include "truetype.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
 
const int stmpeCs = 32;
const int tftCs = 15;
const int tftDc = 33;
const int sdCs = 14;
 
typedef struct {
  int x;
  int y;
} point_t ;
 
point_t *points = NULL;
int numPoints = 0;
 
int *beginPoints;
int numBeginPoints = 0;
int *endPoints;
int numEndPoints = 0;
 
Adafruit_ILI9341 tft = Adafruit_ILI9341(tftCs, tftDc);
truetypeClass ttf1 = truetypeClass(&SD);
truetypeClass ttf2 = truetypeClass(&SD);
 
const char *fontFile1 = "/ipag.ttf";
const char *fontFile2 = "/ipam.ttf";
 
void setup() {
  // put your setup code here, to run once:
  ttGlyph_t glyph;
  Serial.begin(115200);
 
  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK);
 
  ttf1.begin(sdCs, fontFile1);
  ttf2.begin(sdCs, fontFile2);
 
  wchar_t *s1 = L"あいうえお";
  wchar_t *s2 = L"憂鬱薔薇";
  char *s3 = "QuickBrownFoxLazyDog";
 
  tft.fillScreen(ILI9341_BLACK);
 
  for (int i = 0, x = 0; i < wcslen(s1); i++) {
    ttf1.readGlyph(s1[i], &glyph);
    ttf1.adjustGlyph(&glyph);
    x += outputTFT(x, 0, &glyph, &ttf1, 64);
    truetypeClass::freeGlyph(&glyph);
  }
 
  for (int i = 0, x = 0; i < wcslen(s1); i++) {
    ttf2.readGlyph(s1[i], &glyph);
    ttf2.adjustGlyph(&glyph);
    x += outputTFT(x, 50, &glyph, &ttf2, 64);
    truetypeClass::freeGlyph(&glyph);
  }
 
  for (int i = 0, x = 0; i < wcslen(s2); i++) {
    ttf1.readGlyph(s2[i], &glyph);
    ttf1.adjustGlyph(&glyph);
    x += outputTFT(x, 100, &glyph, &ttf1, 64);
    truetypeClass::freeGlyph(&glyph);
  }
 
  for (int i = 0, x = 0; i < wcslen(s2); i++) {
    ttf2.readGlyph(s2[i], &glyph);
    ttf2.adjustGlyph(&glyph);
    x += outputTFT(x, 150, &glyph, &ttf2, 64);
    truetypeClass::freeGlyph(&glyph);
  }
 
  for (int i = 0, x = 0; i < strlen(s3); i++) {
    ttf1.readGlyph(s3[i], &glyph);
    ttf1.adjustGlyph(&glyph);
    x += outputTFT(x, 200, &glyph, &ttf1, 48);
    truetypeClass::freeGlyph(&glyph);
  }
}
 
int getPixel(int x0, int y0, uint8_t *bitmap, int width, int height) {
  return (bitmap[(x0 / 8) + (((width + 7) / 8) * y0)]) & (1 << (7 - x0 % 8));
}
 
void drawPixel(int x0, int y0, uint8_t *bitmap, int width, int height) {
  bitmap[(x0 / 8) + (((width + 7) / 8) * y0)] |= (1 << (7 - x0 % 8));
}
 
/* Bresenham's line algorithm */
void drawLine(int x0, int y0, int x1, int y1, uint8_t *bitmap, int width, int height) {
  int dx = abs(x1 - x0);
  int dy = abs(y1 - y0);
  int sx, sy, err, e2;
 
  if (numPoints == 0) {
    addPoint(x0, y0);
    addBeginPoint(0);
  }
  addPoint(x1, y1);
 
  if (x0 < x1) {
    sx = 1;
  } else {
    sx = -1;
  }
  if (y0 < y1) {
    sy = 1;
  } else {
    sy = -1;
  }
  err = dx - dy;
 
  while (1) {
    drawPixel(x0, y0, bitmap, width, height);
    if ((x0 == x1) && (y0 == y1)) {
      break;
    }
    e2 = 2 * err;
    if (e2 > -dy) {
      err -= dy;
      x0 += sx;
    }
    if (e2 < dx) {
      err += dx;
      y0 += sy;
    }
  }
}
 
int outputTFT(int x, int y, ttGlyph_t *glyph, truetypeClass *ttf, int height) {
  int width;
 
  width = height * (glyph->xMax - glyph->xMin) / (ttf->yMax - ttf->yMin);
 
  int length = (height * (width + 7) / 8);
 
  uint8_t *bitmap = (uint8_t *)calloc(length, sizeof(uint8_t));
 
 
  for (int i = 0, j = 0; i < glyph->numberOfContours; i++, j++) {
    while (j < glyph->endPtsOfContours[i]) {
      int x0, y0, x1, y1;
      if ((glyph->points[j].flag & FLAG_ONCURVE) == 1) {
        if ((glyph->points[j + 1].flag & FLAG_ONCURVE) == 1) {
          drawLine(map(glyph->points[j].x, glyph->xMin, glyph->xMax, 0, width - 1),
                   map(glyph->points[j].y, ttf->yMin, ttf->yMax, height - 1, 0),
                   map(glyph->points[j + 1].x, glyph->xMin, glyph->xMax, 0, width - 1),
                   map(glyph->points[j + 1].y, ttf->yMin, ttf->yMax, height - 1, 0),
                   bitmap, width, height);
        } else { /* off curve */
          x0 = glyph->points[j].x, y0 = glyph->points[j].y;
          for (float t = 0; t <= 1; t += 0.2) {
            x1 = (1 - t) * (1 - t) * glyph->points[j].x + 2 * t * (1 - t) * glyph->points[j + 1].x + t * t * glyph->points[j + 2].x;
            y1 = (1 - t) * (1 - t) * glyph->points[j].y + 2 * t * (1 - t) * glyph->points[j + 1].y + t * t * glyph->points[j + 2].y;
            drawLine(map(x0, glyph->xMin, glyph->xMax, 0, width - 1),
                     map(y0, ttf->yMin, ttf->yMax, height - 1, 0),
                     map(x1, glyph->xMin, glyph->xMax, 0, width - 1),
                     map(y1, ttf->yMin, ttf->yMax, height - 1, 0),
                     bitmap, width, height);
            x0 = x1, y0 = y1;
          }
          drawLine(map(x0, glyph->xMin, glyph->xMax, 0, width - 1),
                   map(y0, ttf->yMin, ttf->yMax, height - 1, 0),
                   map(glyph->points[j + 2].x, glyph->xMin, glyph->xMax, 0, width - 1),
                   map(glyph->points[j + 2].y, ttf->yMin, ttf->yMax, height - 1, 0),
                   bitmap, width, height);
          j++;
        }
      }
      j++;
 
    }
    addEndPoint(numPoints - 1);
    addBeginPoint(numPoints);
  }
 
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      if (isInside(x, y)) {
        drawPixel(x, y, bitmap, width, height);
      }
    }
  }
 
  tft.drawBitmap(x, y, bitmap, width, height, 0xffff);
 
  free(bitmap);
  freePoints();
  freeBeginPoints();
  freeEndPoints();
  return width + 2;
}
 
void addPoint(int x, int y) {
  ++numPoints;
  points = (point_t *)realloc(points, sizeof(point_t) * numPoints);
  points[(numPoints - 1)].x = x;
  points[(numPoints - 1)].y = y;
}
 
void freePoints() {
  free(points);
  points = NULL;
  numPoints = 0;
}
 
void addBeginPoint(int bp) {
  ++numBeginPoints;
  beginPoints = (int *)realloc(beginPoints, sizeof(int) * numBeginPoints);
  beginPoints[(numBeginPoints - 1)] = bp;
}
 
void freeBeginPoints() {
  free(beginPoints);
  beginPoints = NULL;
  numBeginPoints = 0;
}
 
void addEndPoint(int ep) {
  ++numEndPoints;
  endPoints = (int *)realloc(endPoints, sizeof(int) * numEndPoints);
  endPoints[(numEndPoints - 1)] = ep;
}
 
void freeEndPoints() {
  free(endPoints);
  endPoints = NULL;
  numEndPoints = 0;
}
 
bool isInside(int x, int y) {
  int windingNumber = 0;
  int bpCounter = 0, epCounter = 0;
  point_t point = {x, y};
 
  point_t point1;
  point_t point2;
 
  for (int i = 0; i < numPoints; ++i) {
    point1.x = points[i].x;
    point1.y = points[i].y;
    // Wrap?
    if (i == endPoints[epCounter]) {
      point2.x = points[beginPoints[bpCounter]].x;
      point2.y = points[beginPoints[bpCounter]].y;
      epCounter++;
      bpCounter++;
    } else {
      point2.x = points[i + 1].x;
      point2.y = points[i + 1].y;
    }
 
    if (point1.y <= point.y) {
      if (point2.y > point.y) {
        if (isLeft(point1, point2, point) > 0) {
          ++windingNumber;  
        }
      }
    } else {
      // start y > point.y (no test needed)
      if (point2.y <= point.y) {
        if (isLeft(point1, point2, point) < 0) {
          --windingNumber;
        }
      }
    }
  }
 
  return (windingNumber != 0);
}
 
int isLeft(point_t &p0, point_t &p1, point_t &point) {
  return ((p1.x - p0.x) * (point.y - p0.y) - (point.x - p0.x) * (p1.y - p0.y));
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

バージョン

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

最終更新日

June 20, 2020

inserted by FC2 system