summaryrefslogtreecommitdiff
path: root/src/ui/text.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:06:52 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-21 15:07:38 +0300
commitd773b499e595a43b9b1ae449262dcf13cabf2d02 (patch)
treeb1baeb12025a04f8316636b5d0ab18e30ceedb2c /src/ui/text.c
Initial commit
Borrowing the app skeleton from Bitwise Harmony.
Diffstat (limited to 'src/ui/text.c')
-rw-r--r--src/ui/text.c533
1 files changed, 533 insertions, 0 deletions
diff --git a/src/ui/text.c b/src/ui/text.c
new file mode 100644
index 00000000..ac211481
--- /dev/null
+++ b/src/ui/text.c
@@ -0,0 +1,533 @@
1#include "text.h"
2#include "color.h"
3#include "metrics.h"
4#include "embedded.h"
5#include "app.h"
6
7#define STB_TRUETYPE_IMPLEMENTATION
8#include "../stb_truetype.h"
9
10#include <the_Foundation/array.h>
11#include <the_Foundation/file.h>
12#include <the_Foundation/hash.h>
13#include <the_Foundation/math.h>
14#include <the_Foundation/path.h>
15#include <the_Foundation/vec2.h>
16
17#include <SDL_surface.h>
18#include <stdarg.h>
19
20iDeclareType(Glyph)
21iDeclareTypeConstructionArgs(Glyph, iChar ch)
22
23struct Impl_Glyph {
24 iHashNode node;
25 iRect rect;
26 int advance;
27 int dx, dy;
28};
29
30void init_Glyph(iGlyph *d, iChar ch) {
31 d->node.key = ch;
32 d->rect = zero_Rect();
33 d->advance = 0;
34}
35
36void deinit_Glyph(iGlyph *d) {
37 iUnused(d);
38}
39
40iChar char_Glyph(const iGlyph *d) {
41 return d->node.key;
42}
43
44iDefineTypeConstructionArgs(Glyph, (iChar ch), ch)
45
46/*-----------------------------------------------------------------------------------------------*/
47
48iDeclareType(Font)
49
50struct Impl_Font {
51 iBlock * data;
52 stbtt_fontinfo font;
53 float scale;
54 int height;
55 int baseline;
56 iHash glyphs;
57};
58
59static void init_Font(iFont *d, const iBlock *data, int height) {
60 init_Hash(&d->glyphs);
61 d->data = NULL;
62 d->height = height;
63 iZap(d->font);
64 stbtt_InitFont(&d->font, constData_Block(data), 0);
65 d->scale = stbtt_ScaleForPixelHeight(&d->font, height);
66 int ascent;
67 stbtt_GetFontVMetrics(&d->font, &ascent, 0, 0);
68 d->baseline = (int) ascent * d->scale;
69}
70
71static void deinit_Font(iFont *d) {
72 iForEach(Hash, i, &d->glyphs) {
73 delete_Glyph((iGlyph *) i.value);
74 }
75 deinit_Hash(&d->glyphs);
76 delete_Block(d->data);
77}
78
79iDeclareType(Text)
80
81struct Impl_Text {
82 iFont fonts[max_FontId];
83 SDL_Renderer *render;
84 SDL_Texture * cache;
85 iInt2 cacheSize;
86 iInt2 cachePos;
87 int cacheRowHeight;
88 SDL_Palette * grayscale;
89};
90
91static iText text_;
92
93void init_Text(SDL_Renderer *render) {
94 iText *d = &text_;
95 d->render = render;
96 /* A grayscale palette for rasterized glyphs. */ {
97 SDL_Color colors[256];
98 for (int i = 0; i < 256; ++i) {
99 colors[i] = (SDL_Color){ 255, 255, 255, i };
100 }
101 d->grayscale = SDL_AllocPalette(256);
102 SDL_SetPaletteColors(d->grayscale, colors, 0, 256);
103 }
104 /* Initialize the glyph cache. */ {
105 d->cacheSize = init1_I2(fontSize_UI * 16);
106 d->cachePos = zero_I2();
107 d->cache = SDL_CreateTexture(render,
108 SDL_PIXELFORMAT_RGBA8888,
109 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
110 d->cacheSize.x,
111 d->cacheSize.y);
112 SDL_SetTextureBlendMode(d->cache, SDL_BLENDMODE_BLEND);
113 d->cacheRowHeight = 0;
114 }
115 /* Load the fonts. */ {
116 const struct { const iBlock *ttf; int size; } fontData[max_FontId] = {
117 { &fontFiraSansRegular_Embedded, fontSize_UI },
118 { &fontFiraSansRegular_Embedded, fontSize_UI },
119 { &fontFiraMonoRegular_Embedded, fontSize_UI },
120 { &fontFiraSansRegular_Embedded, fontSize_UI },
121 { &fontFiraSansRegular_Embedded, fontSize_UI * 1.5f },
122 { &fontFiraMonoRegular_Embedded, fontSize_UI },
123 { &fontFiraSansLightItalic_Embedded, fontSize_UI },
124 { &fontFiraSansRegular_Embedded, fontSize_UI * 2.5f },
125 { &fontFiraSansRegular_Embedded, fontSize_UI * 2.0f },
126 { &fontFiraSansRegular_Embedded, fontSize_UI * 1.5f },
127 };
128 iForIndices(i, fontData) {
129 init_Font(&d->fonts[i], fontData[i].ttf, fontData[i].size);
130 }
131 }
132}
133
134void deinit_Text(void) {
135 iText *d = &text_;
136 SDL_FreePalette(d->grayscale);
137 iForIndices(i, d->fonts) {
138 deinit_Font(&d->fonts[i]);
139 }
140 SDL_DestroyTexture(d->cache);
141 d->render = NULL;
142}
143
144static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, iChar ch) {
145 int w, h;
146 uint8_t *bmp = stbtt_GetCodepointBitmap(&d->font, d->scale, d->scale, ch, &w, &h, 0, 0);
147 /* Note: `bmp` must be freed afterwards. */
148 SDL_Surface *surface =
149 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
150 SDL_SetSurfacePalette(surface, text_.grayscale);
151 return surface;
152}
153
154static iBool isSpecialChar_(iChar ch) {
155 return ch >= specialSymbol_Text && ch < 0x20;
156}
157
158static float symbolEmWidth_(int symbol) {
159 return 1.5f;
160}
161
162static float symbolAdvance_(int symbol) {
163 return 1.5f;
164}
165
166static int specialChar_(iChar ch) {
167 return ch - specialSymbol_Text;
168}
169
170iLocalDef SDL_Rect sdlRect_(const iRect rect) {
171 return (SDL_Rect){ rect.pos.x, rect.pos.y, rect.size.x, rect.size.y };
172}
173
174static void fillTriangle_(SDL_Surface *surface, const SDL_Rect *rect, int dir) {
175 const uint32_t color = 0xffffffff;
176 SDL_LockSurface(surface);
177 uint32_t *row = surface->pixels;
178 row += rect->x + rect->y * surface->pitch / 4;
179 for (int y = 0; y < rect->h; y++, row += surface->pitch / 4) {
180 float norm = (float) y / (float) (rect->h - 1) * 2.0f;
181 if (norm > 1.0f) norm = 2.0f - norm;
182 const int len = norm * rect->w;
183 const float fract = norm * rect->w - len;
184 const uint32_t fractColor = 0xffffff00 | (int) (fract * 0xff);
185 if (dir > 0) {
186 for (int x = 0; x < len; x++) {
187 row[x] = color;
188 }
189 if (len < rect->w) {
190 row[len] = fractColor;
191 }
192 }
193 else {
194 for (int x = 0; x < len; x++) {
195 row[rect->w - len + x] = color;
196 }
197 if (len < rect->w) {
198 row[rect->w - len - 1] = fractColor;
199 }
200 }
201 }
202 SDL_UnlockSurface(surface);
203}
204
205static void cache_Font_(iFont *d, iGlyph *glyph) {
206 iText *txt = &text_;
207 SDL_Renderer *render = txt->render;
208 SDL_Texture *tex = NULL;
209 SDL_Surface *surface = NULL;
210 const iChar ch = char_Glyph(glyph);
211 iBool fromStb = iFalse;
212 if (!isSpecialChar_(ch)) {
213 /* Rasterize the glyph using stbtt. */
214 surface = rasterizeGlyph_Font_(d, ch);
215 stbtt_GetCodepointBitmapBox(
216 &d->font, ch, d->scale, d->scale, &glyph->dx, &glyph->dy, NULL, NULL);
217 fromStb = iTrue;
218 tex = SDL_CreateTextureFromSurface(render, surface);
219 glyph->rect.size = init_I2(surface->w, surface->h);
220 }
221 else {
222 /* Metrics for special symbols. */
223 int em, lsb;
224 const int symbol = specialChar_(ch);
225 stbtt_GetCodepointHMetrics(&d->font, 'M', &em, &lsb);
226 glyph->dx = d->baseline / 10;
227 glyph->dy = -d->baseline;
228 glyph->rect.size = init_I2(symbolEmWidth_(symbol) * em * d->scale, d->height);
229#if 0
230 if (isRasterizedSymbol_(ch)) {
231 /* Rasterize manually. */
232 surface = SDL_CreateRGBSurfaceWithFormat(
233 0, width_Rect(glyph->rect), height_Rect(glyph->rect), 32, SDL_PIXELFORMAT_RGBA8888);
234 SDL_FillRect(surface, NULL, 0);
235 const uint32_t white = 0xffffffff;
236 switch (specialChar_(ch)) {
237 case play_SpecialSymbol:
238 fillTriangle_(surface, &(SDL_Rect){ 0, 0, surface->w, d->baseline }, 1);
239 break;
240 case pause_SpecialSymbol: {
241 const int w = surface->w * 4 / 11;
242 SDL_FillRect(surface, &(SDL_Rect){ 0, 0, w, d->baseline }, white);
243 SDL_FillRect(surface, &(SDL_Rect){ surface->w - w, 0, w, d->baseline }, white);
244 break;
245 }
246 case rewind_SpecialSymbol: {
247 const int w1 = surface->w / 7;
248 const int w2 = surface->w * 3 / 7;
249 const int h = d->baseline * 4 / 5;
250 const int off = (d->baseline - h) / 2;
251 SDL_FillRect(surface, &(SDL_Rect){ 0, off, w1, h}, white);
252 fillTriangle_(surface, &(SDL_Rect){ w1, off, w2, h }, -1);
253 fillTriangle_(surface, &(SDL_Rect){ surface->w * 4 / 7, off, w2, h }, -1);
254 break;
255 }
256 }
257 tex = SDL_CreateTextureFromSurface(render, surface);
258 }
259#endif
260 }
261 /* Determine placement in the glyph cache texture, advancing in rows. */
262 if (txt->cachePos.x + glyph->rect.size.x > txt->cacheSize.x) {
263 txt->cachePos.x = 0;
264 txt->cachePos.y += txt->cacheRowHeight;
265 txt->cacheRowHeight = 0;
266 }
267 glyph->rect.pos = txt->cachePos;
268 SDL_SetRenderTarget(render, txt->cache);
269 const SDL_Rect dstRect = sdlRect_(glyph->rect);
270 if (surface) {
271 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect);
272 }
273 else {
274 /* Draw a special symbol. */
275 SDL_SetRenderDrawColor(render, 255, 255, 255, 255);
276 const iInt2 tl = init_I2(dstRect.x, dstRect.y);
277 const iInt2 br = init_I2(dstRect.x + dstRect.w - 1, dstRect.y + dstRect.h - 1);
278 const int midX = tl.x + dstRect.w / 2;
279 const int midY = tl.y + dstRect.h / 2;
280 const int symH = dstRect.h * 2 / 6;
281#if 0
282 /* Frame. */
283 if (isFramedSymbol_(ch)) {
284 SDL_RenderDrawLines(
285 render,
286 (SDL_Point[]){
287 { tl.x, tl.y }, { br.x, tl.y }, { br.x, br.y }, { tl.x, br.y }, { tl.x, tl.y } },
288 5);
289 }
290 iArray points;
291 init_Array(&points, sizeof(SDL_Point));
292 switch (specialChar_(ch)) {
293 case 0: /* silence */
294 break;
295 case 1: /* sine */
296 for (int i = 0; i < dstRect.w; ++i) {
297 float rad = 2.0f * iMathPif * (float) i / dstRect.w;
298 SDL_Point pt = { tl.x + i, midY + sin(rad) * symH};
299 pushBack_Array(&points, &pt);
300 }
301 SDL_RenderDrawLines(render, constData_Array(&points), size_Array(&points));
302 break;
303 case 2: /* square */
304 SDL_RenderDrawLines(render,
305 (SDL_Point[]){ { tl.x, midY - symH },
306 { midX, midY - symH },
307 { midX, midY + symH },
308 { br.x, midY + symH } },
309 4);
310 break;
311 case 3: /* saw */
312 SDL_RenderDrawLines(render,
313 (SDL_Point[]){ { tl.x, midY },
314 { midX, midY - symH },
315 { midX, midY + symH },
316 { br.x, midY } },
317 4);
318 break;
319 case 4: /* triangle */
320 SDL_RenderDrawLines(render,
321 (SDL_Point[]){ { tl.x, midY },
322 { tl.x + dstRect.w / 4, midY - symH },
323 { br.x - dstRect.w / 4, midY + symH },
324 { br.x, midY } },
325 4);
326 break;
327 case 5: /* noise */
328 for (int i = 0; i < dstRect.w; ++i) {
329 for (int p = 0; p < 2; ++p) {
330 const float val = iRandomf() * 2.0f - 1.0f;
331 pushBack_Array(&points, &(SDL_Point){ tl.x + i, midY - val * symH });
332 }
333 }
334 SDL_RenderDrawPoints(render, constData_Array(&points), size_Array(&points));
335 break;
336 }
337 deinit_Array(&points);
338#endif
339 }
340 SDL_SetRenderTarget(render, NULL);
341 if (tex) {
342 SDL_DestroyTexture(tex);
343 iAssert(surface);
344 if (fromStb) stbtt_FreeBitmap(surface->pixels, NULL);
345 SDL_FreeSurface(surface);
346 }
347 /* Update cache cursor. */
348 txt->cachePos.x += glyph->rect.size.x;
349 txt->cacheRowHeight = iMax(txt->cacheRowHeight, glyph->rect.size.y);
350}
351
352static const iGlyph *glyph_Font_(iFont *d, iChar ch) {
353 const void *node = value_Hash(&d->glyphs, ch);
354 if (node) {
355 return node;
356 }
357 iGlyph *glyph = new_Glyph(ch);
358 cache_Font_(d, glyph);
359 insert_Hash(&d->glyphs, &glyph->node);
360 return glyph;
361}
362
363enum iRunMode { measure_RunMode, draw_RunMode, drawPermanentColor_RunMode };
364
365static iInt2 run_Font_(iFont *d, enum iRunMode mode, const char *text, size_t maxLen, iInt2 pos,
366 int *runAdvance_out) {
367 iInt2 size = zero_I2();
368 const iInt2 orig = pos;
369 const stbtt_fontinfo *info = &d->font;
370 float xpos = pos.x;
371 float xposMax = xpos;
372 const iString textStr = iStringLiteral(text);
373 iConstForEach(String, i, &textStr) {
374 iChar ch = i.value;
375 /* Special instructions. */ {
376 if (ch == '\n') {
377 xpos = pos.x;
378 pos.y += d->height;
379 continue;
380 }
381 if (ch == '\r') {
382 next_StringConstIterator(&i);
383 const iColor clr = get_Color(i.value - '0');
384 if (mode == draw_RunMode) {
385 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b);
386 }
387 continue;
388 }
389 }
390 const iGlyph *glyph = glyph_Font_(d, ch);
391 int x1 = iRound(xpos);
392 int x2 = x1 + glyph->rect.size.x;
393 size.x = iMax(size.x, x2 - orig.x);
394 size.y = iMax(size.y, pos.y + d->height - orig.y);
395 if (mode != measure_RunMode) {
396 SDL_Rect dst = { x1 + glyph->dx,
397 pos.y + d->baseline + glyph->dy,
398 glyph->rect.size.x,
399 glyph->rect.size.y };
400 SDL_RenderCopy(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect, &dst);
401 }
402 int advance, lsb;
403 const iBool spec = isSpecialChar_(ch);
404 stbtt_GetCodepointHMetrics(info, spec ? 'M' : ch, &advance, &lsb);
405 if (spec) {
406 advance *= symbolAdvance_(specialChar_(ch));
407 }
408 xpos += d->scale * advance;
409 xposMax = iMax(xposMax, xpos);
410 /* Check the next character. */ {
411 iStringConstIterator j = i;
412 next_StringConstIterator(&j);
413 const iChar next = j.value;
414 if (next) {
415 xpos += d->scale * stbtt_GetCodepointKernAdvance(info, ch, next);
416 }
417 }
418 if (--maxLen == 0) {
419 break;
420 }
421 }
422 if (runAdvance_out) {
423 *runAdvance_out = xposMax - orig.x;
424 }
425 return size;
426}
427
428int lineHeight_Text(int fontId) {
429 return text_.fonts[fontId].height;
430}
431
432iInt2 measure_Text(int fontId, const char *text) {
433 if (!*text) {
434 return init_I2(0, lineHeight_Text(fontId));
435 }
436 return run_Font_(&text_.fonts[fontId], measure_RunMode, text, iInvalidSize, zero_I2(), NULL);
437}
438
439iInt2 advance_Text(int fontId, const char *text) {
440 int advance;
441 const int height =
442 run_Font_(&text_.fonts[fontId], measure_RunMode, text, iInvalidSize, zero_I2(), &advance).y;
443 return init_I2(advance, height); //lineHeight_Text(fontId));
444}
445
446iInt2 advanceN_Text(int fontId, const char *text, size_t n) {
447 if (n == 0) {
448 return init_I2(0, lineHeight_Text(fontId));
449 }
450 int advance;
451 run_Font_(&text_.fonts[fontId], measure_RunMode, text, n, zero_I2(), &advance);
452 return init_I2(advance, lineHeight_Text(fontId));
453}
454
455static void draw_Text_(int fontId, iInt2 pos, int color, const char *text) {
456 iText *d = &text_;
457 const iColor clr = get_Color(color & mask_ColorId);
458 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);
459 run_Font_(&d->fonts[fontId],
460 color & permanent_ColorId ? drawPermanentColor_RunMode : draw_RunMode,
461 text,
462 iInvalidSize,
463 pos,
464 NULL);
465}
466
467void draw_Text(int fontId, iInt2 pos, int color, const char *text, ...) {
468 iBlock chars;
469 init_Block(&chars, 0); {
470 va_list args;
471 va_start(args, text);
472 vprintf_Block(&chars, text, args);
473 va_end(args);
474 }
475 if (pos.x < 0) {
476 /* Right-aligned. */
477 pos.x = -pos.x - measure_Text(fontId, cstr_Block(&chars)).x;
478 }
479 if (pos.y < 0) {
480 /* Bottom-aligned. */
481 pos.y = -pos.y - lineHeight_Text(fontId);
482 }
483 draw_Text_(fontId, pos, color, cstr_Block(&chars));
484 deinit_Block(&chars);
485}
486
487void drawCentered_Text(int fontId, iRect rect, int color, const char *text, ...) {
488 iBlock chars;
489 init_Block(&chars, 0); {
490 va_list args;
491 va_start(args, text);
492 vprintf_Block(&chars, text, args);
493 va_end(args);
494 }
495 const iInt2 textSize = advance_Text(fontId, cstr_Block(&chars));
496 draw_Text_(fontId, sub_I2(mid_Rect(rect), divi_I2(textSize, 2)), color, cstr_Block(&chars));
497 deinit_Block(&chars);
498}
499
500SDL_Texture *glyphCache_Text(void) {
501 return text_.cache;
502}
503
504/*-----------------------------------------------------------------------------------------------*/
505
506iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text)
507
508void init_TextBuf(iTextBuf *d, int font, const char *text) {
509 SDL_Renderer *render = text_.render;
510 d->size = advance_Text(font, text);
511 d->texture = SDL_CreateTexture(render,
512 SDL_PIXELFORMAT_RGBA8888,
513 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
514 d->size.x,
515 d->size.y);
516 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);
517 SDL_SetRenderTarget(render, d->texture);
518 draw_Text_(font, zero_I2(), white_ColorId, text);
519 SDL_SetRenderTarget(render, NULL);
520}
521
522void deinit_TextBuf(iTextBuf *d) {
523 SDL_DestroyTexture(d->texture);
524}
525
526void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) {
527 const iColor clr = get_Color(color);
528 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b);
529 SDL_RenderCopy(text_.render,
530 d->texture,
531 &(SDL_Rect){ 0, 0, d->size.x, d->size.y },
532 &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y });
533}