TinyGPU
Loading...
Searching...
No Matches
BitmapFont.h
Go to the documentation of this file.
1#pragma once
2
3#include <stddef.h>
4#include <stdint.h>
5
6#include <array>
7
8#include "TinyGPU/Font/IFont.h"
9#include "TinyGPU/Surface/ISurface.h"
10#include "TinyGPU/Color/RGB565.h"
11
12namespace tinygpu {
13
14/**
15 * @brief Fixed-size bitmap font backed by a caller-supplied glyph table.
16 *
17 * @tparam Width Glyph width in pixels.
18 * @tparam Height Glyph height in pixels.
19 * @tparam RowT Unsigned integer type used to store one glyph row. Only the
20 * `Width` most significant bits of each row are used (bit `Width-1` is the
21 * left-most pixel), mirroring the bit order used by Font5x7.
22 * @tparam RGB_T The pixel color type. Can be RGB565, RGB666, RGB888, etc.
23 *
24 * Concrete fonts (e.g. converted from vendor font tables) subclass this and
25 * pass their own glyph table to the constructor. Only a contiguous ASCII
26 * range starting at `firstChar` is supported; any code point outside that
27 * range falls back to the glyph for '?'.
28 */
29template <uint8_t Width, uint8_t Height, typename RowT, typename RGB_T = RGB565>
30class BitmapFont : public IFont<RGB_T> {
31 public:
32 /// Glyph storage: one row entry per pixel row.
33 using Glyph = std::array<RowT, Height>;
34 /// Unicode code point type used during UTF-8 decoding.
35 using CodePoint = uint32_t;
36
37 /// Width of a glyph in pixels.
38 static constexpr uint8_t kGlyphWidth = Width;
39 /// Height of a glyph in pixels.
40 static constexpr uint8_t kGlyphHeight = Height;
41 /// Advance width used in proportional mode for glyphs with no set pixels
42 /// (e.g. the space character).
43 static constexpr uint8_t kBlankGlyphWidth = (kGlyphWidth > 2) ? kGlyphWidth - 2 : kGlyphWidth;
44
45 /// Creates a fixed bitmap font over a contiguous glyph table.
46 BitmapFont(const Glyph* table, size_t glyphCount, char firstChar = ' ')
47 : table_(table), glyphCount_(glyphCount), firstChar_(firstChar) {}
49 /// Enables/disables proportional spacing based on each glyph's effective
50 /// (used) width instead of the fixed glyph width.
51 void setProportional(bool proportional) override { proportional_ = proportional; }
52 /// Returns whether proportional spacing is enabled.
53 bool isProportional() const override { return proportional_; }
54
55 /// Returns the left-most column (0-based) that has a set pixel in a code
56 /// point's glyph. Glyphs with no set pixels (e.g. the space) return 0.
57 uint8_t glyphLeftOffset(CodePoint codePoint) const {
58 uint8_t left = 0;
59 uint8_t right = 0;
60 glyphColumnBounds(codePoint, left, right);
61 return left;
62 }
63
64 /// Returns the effective (used) width in pixels of a code point's glyph,
65 /// i.e. the span from its left-most to right-most set pixel column,
66 /// inclusive. Glyphs with no set pixels (e.g. the space) fall back to a
67 /// default blank width.
68 uint8_t glyphEffectiveWidth(CodePoint codePoint) const {
69 uint8_t left = 0;
70 uint8_t right = 0;
71 if (!glyphColumnBounds(codePoint, left, right)) {
72 return kBlankGlyphWidth;
73 }
74 return static_cast<uint8_t>(right - left + 1);
75 }
76
77 /// Returns the advance width (in unscaled pixels) used to move the cursor
78 /// past a code point's glyph, honoring proportional mode when enabled.
79 uint8_t glyphAdvanceWidth(CodePoint codePoint) const {
80 return proportional_ ? glyphEffectiveWidth(codePoint) : kGlyphWidth;
81 }
82
83 /// Returns the glyph for an 8-bit character.
84 const Glyph& glyph(char character) const {
85 return glyph(static_cast<CodePoint>(static_cast<unsigned char>(character)));
86 }
87
88 /// Returns the glyph for a Unicode code point. Virtual so a subclass
89 /// with its own extra fallback logic (e.g. Font5x7's mapping of
90 /// Latin-1/CP1252-ish codepoints outside its stored ASCII range) can
91 /// override just this lookup and get every other method here - pixel(),
92 /// drawText(), measureTextWidth(), ... - for free, since they all call
93 /// glyph() internally rather than duplicating the lookup themselves.
94 virtual const Glyph& glyph(CodePoint codePoint) const {
95 const auto first = static_cast<unsigned char>(firstChar_);
96 if (codePoint >= first && codePoint < first + glyphCount_) {
97 return table_[codePoint - first];
98 }
99 return replacementGlyph();
100 }
101
102 /// Returns whether a pixel is set in a code point glyph.
103 bool pixel(CodePoint codePoint, uint8_t x, uint8_t y) const {
104 if (x >= kGlyphWidth || y >= kGlyphHeight) {
105 return false;
106 }
107 const RowT rowMask = glyph(codePoint)[y];
108 return (rowMask & static_cast<RowT>(RowT(1) << (kGlyphWidth - 1 - x))) != 0;
109 }
110
111 /// Draws a single Unicode code point. In proportional mode, leading blank
112 /// columns of the glyph are skipped so the visible pixels start at `x`.
113 void drawCodePoint(ISurface<RGB_T>& target, int16_t x, int16_t y,
114 CodePoint codePoint, RGB_T foreground,
115 RGB_T background = RGB_T(0), bool opaque = false,
116 uint8_t scale = 1) const {
117 if (scale == 0) {
118 scale = 1;
119 }
120
121 uint8_t startColumn = 0;
122 uint8_t endColumn = kGlyphWidth;
123 if (proportional_) {
124 uint8_t left = 0;
125 uint8_t right = 0;
126 if (glyphColumnBounds(codePoint, left, right)) {
127 startColumn = left;
128 endColumn = static_cast<uint8_t>(right + 1);
129 } else {
130 endColumn = kBlankGlyphWidth;
131 }
132 }
133
134 for (uint8_t row = 0; row < kGlyphHeight; ++row) {
135 for (uint8_t column = startColumn; column < endColumn; ++column) {
136 const int16_t pixelX =
137 static_cast<int16_t>(x + ((column - startColumn) * scale));
138 const int16_t pixelY = static_cast<int16_t>(y + (row * scale));
139
140 if (pixel(codePoint, column, row)) {
141 target.fillRect(pixelX, pixelY, scale, scale, foreground);
142 } else if (opaque) {
143 target.fillRect(pixelX, pixelY, scale, scale, background);
144 }
145 }
146 }
147 }
148
149 /// Draws a single character.
150 void drawChar(ISurface<RGB_T>& target, int16_t x, int16_t y, char character,
151 RGB_T foreground, RGB_T background = RGB_T(0),
152 bool opaque = false, uint8_t scale = 1) const {
153 drawCodePoint(target, x, y,
154 static_cast<CodePoint>(static_cast<unsigned char>(character)),
155 foreground, background, opaque, scale);
156 }
157
158 /// Draws a UTF-8 text string.
159 void drawText(ISurface<RGB_T>& target, int16_t x, int16_t y, const char* text,
160 RGB_T foreground, RGB_T background = RGB_T(0),
161 bool opaque = false, uint8_t scale = 1, uint8_t spacing = 1,
162 uint8_t lineSpacing = 1) const override {
163 if (text == nullptr) {
164 return;
165 }
166 if (scale == 0) {
167 scale = 1;
168 }
169
170 const int16_t advanceY =
171 static_cast<int16_t>((kGlyphHeight * scale) + lineSpacing);
172
173 int16_t cursorX = x;
174 int16_t cursorY = y;
175 const char* current = text;
176 while (*current != '\0') {
177 if (*current == '\n') {
178 ++current;
179 cursorX = x;
180 cursorY = static_cast<int16_t>(cursorY + advanceY);
181 continue;
182 }
183
184 const CodePoint codePoint = decodeNextUtf8(current);
185 drawCodePoint(target, cursorX, cursorY, codePoint, foreground, background,
186 opaque, scale);
187 const int16_t advanceX = static_cast<int16_t>(
188 (glyphAdvanceWidth(codePoint) * scale) + spacing);
189 cursorX = static_cast<int16_t>(cursorX + advanceX);
190 }
191 }
192
193 /// Returns the width of the longest text line in pixels.
195 uint8_t spacing = 1) const override {
196 if (text == nullptr || *text == '\0') {
197 return 0;
198 }
199 if (scale == 0) {
200 scale = 1;
201 }
202
203 size_t lineWidth = 0;
204 size_t lineChars = 0;
205 size_t longestLineWidth = 0;
206 const char* current = text;
207 while (*current != '\0') {
208 if (*current == '\n') {
209 ++current;
210 if (lineWidth > longestLineWidth) {
211 longestLineWidth = lineWidth;
212 }
213 lineWidth = 0;
214 lineChars = 0;
215 } else {
216 const CodePoint codePoint = decodeNextUtf8(current);
217 if (lineChars > 0) {
218 lineWidth += spacing;
219 }
220 lineWidth += static_cast<size_t>(glyphAdvanceWidth(codePoint)) * scale;
221 ++lineChars;
222 }
223 }
224
225 if (lineWidth > longestLineWidth) {
226 longestLineWidth = lineWidth;
227 }
228
229 return longestLineWidth;
230 }
231
232 /// Returns the total text height in pixels.
234 uint8_t lineSpacing = 1) const override {
235 if (text == nullptr || *text == '\0') {
236 return 0;
237 }
238 if (scale == 0) {
239 scale = 1;
240 }
241
242 size_t lines = 1;
243 for (const char* current = text; *current != '\0'; ++current) {
244 if (*current == '\n') {
245 ++lines;
246 }
247 }
248
249 return (lines * kGlyphHeight * scale) + ((lines - 1) * lineSpacing);
250 }
251
252 /// Returns the scaled glyph height in pixels.
253 size_t getHeight(uint8_t scale) const override {
254 return (kGlyphHeight * scale);
255 }
256
257 private:
258 const Glyph* table_;
259 size_t glyphCount_;
260 char firstChar_;
261 bool proportional_ = true;
262
263 /// Finds the left-most and right-most columns (0-based, inclusive) that
264 /// have a set pixel in a code point's glyph. Returns false, leaving
265 /// `leftColumn`/`rightColumn` unchanged, if the glyph has no set pixels.
266 bool glyphColumnBounds(CodePoint codePoint, uint8_t& leftColumn,
267 uint8_t& rightColumn) const {
268 const Glyph& glyphData = glyph(codePoint);
269 bool any = false;
270 uint8_t left = kGlyphWidth;
271 uint8_t right = 0;
272 for (uint8_t row = 0; row < kGlyphHeight; ++row) {
273 const RowT rowMask = glyphData[row];
274 if (rowMask == 0) {
275 continue;
276 }
277 for (uint8_t column = 0; column < kGlyphWidth; ++column) {
278 if ((rowMask & static_cast<RowT>(RowT(1) << (kGlyphWidth - 1 - column))) !=
279 0) {
280 any = true;
281 if (column < left) {
282 left = column;
283 }
284 if (column > right) {
285 right = column;
286 }
287 }
288 }
289 }
290
291 if (!any) {
292 return false;
293 }
294 leftColumn = left;
295 rightColumn = right;
296 return true;
297 }
298
299 const Glyph& replacementGlyph() const {
300 const auto first = static_cast<unsigned char>(firstChar_);
301 const auto question = static_cast<unsigned char>('?');
302 if (question >= first && question < first + glyphCount_) {
303 return table_[question - first];
304 }
305 return table_[0];
306 }
307
308 CodePoint decodeNextUtf8(const char*& current) const {
309 const unsigned char firstByte = static_cast<unsigned char>(*current);
310 if (firstByte == 0) {
311 return 0;
312 }
313
314 if ((firstByte & 0x80U) == 0) {
315 ++current;
316 return static_cast<CodePoint>(firstByte);
317 }
318
319 uint8_t sequenceLength = 0;
320 CodePoint codePoint = 0;
321 if ((firstByte & 0xE0U) == 0xC0U) {
322 sequenceLength = 2;
323 codePoint = static_cast<CodePoint>(firstByte & 0x1FU);
324 } else if ((firstByte & 0xF0U) == 0xE0U) {
325 sequenceLength = 3;
326 codePoint = static_cast<CodePoint>(firstByte & 0x0FU);
327 } else if ((firstByte & 0xF8U) == 0xF0U) {
328 sequenceLength = 4;
329 codePoint = static_cast<CodePoint>(firstByte & 0x07U);
330 } else {
331 ++current;
332 return static_cast<CodePoint>('?');
333 }
334
335 for (uint8_t index = 1; index < sequenceLength; ++index) {
336 const unsigned char nextByte = static_cast<unsigned char>(current[index]);
337 if (nextByte == 0 || (nextByte & 0xC0U) != 0x80U) {
338 ++current;
339 return static_cast<CodePoint>('?');
340 }
341
342 codePoint = static_cast<CodePoint>((codePoint << 6) | (nextByte & 0x3FU));
343 }
344
345 current += sequenceLength;
346 return codePoint;
347 }
348};
349
350} // namespace tinygpu
Fixed-size bitmap font backed by a caller-supplied glyph table.
Definition: BitmapFont.h:30
virtual const Glyph & glyph(CodePoint codePoint) const
Definition: BitmapFont.h:94
void drawChar(ISurface< RGB_T > &target, int16_t x, int16_t y, char character, RGB_T foreground, RGB_T background=RGB_T(0), bool opaque=false, uint8_t scale=1) const
Draws a single character.
Definition: BitmapFont.h:150
size_t getHeight(uint8_t scale) const override
Returns the scaled glyph height in pixels.
Definition: BitmapFont.h:253
static constexpr uint8_t kGlyphWidth
Width of a glyph in pixels.
Definition: BitmapFont.h:38
static constexpr uint8_t kBlankGlyphWidth
Definition: BitmapFont.h:43
uint8_t glyphAdvanceWidth(CodePoint codePoint) const
Definition: BitmapFont.h:79
uint8_t glyphEffectiveWidth(CodePoint codePoint) const
Definition: BitmapFont.h:68
void drawText(ISurface< RGB_T > &target, int16_t x, int16_t y, const char *text, RGB_T foreground, RGB_T background=RGB_T(0), bool opaque=false, uint8_t scale=1, uint8_t spacing=1, uint8_t lineSpacing=1) const override
Draws a UTF-8 text string.
Definition: BitmapFont.h:159
static constexpr uint8_t kGlyphHeight
Height of a glyph in pixels.
Definition: BitmapFont.h:40
bool pixel(CodePoint codePoint, uint8_t x, uint8_t y) const
Returns whether a pixel is set in a code point glyph.
Definition: BitmapFont.h:103
size_t measureTextHeight(const char *text, uint8_t scale=1, uint8_t lineSpacing=1) const override
Returns the total text height in pixels.
Definition: BitmapFont.h:233
void drawCodePoint(ISurface< RGB_T > &target, int16_t x, int16_t y, CodePoint codePoint, RGB_T foreground, RGB_T background=RGB_T(0), bool opaque=false, uint8_t scale=1) const
Definition: BitmapFont.h:113
size_t measureTextWidth(const char *text, uint8_t scale=1, uint8_t spacing=1) const override
Returns the width of the longest text line in pixels.
Definition: BitmapFont.h:194
void setProportional(bool proportional) override
Definition: BitmapFont.h:51
bool isProportional() const override
Returns whether proportional spacing is enabled.
Definition: BitmapFont.h:53
BitmapFont(const Glyph *table, size_t glyphCount, char firstChar=' ')
Creates a fixed bitmap font over a contiguous glyph table.
Definition: BitmapFont.h:46
const Glyph & glyph(char character) const
Returns the glyph for an 8-bit character.
Definition: BitmapFont.h:84
uint8_t glyphLeftOffset(CodePoint codePoint) const
Definition: BitmapFont.h:57
Font rendering interface for TinyGPU-compatible framebuffers.
Definition: IFont.h:19
RGB color stored in 16-bit RGB565 format, byte-swapped from the conventional bit layout.
Definition: RGB565.h:24
Definition: DSIBusESP32.h:19