diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-21 15:06:52 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-21 15:07:38 +0300 |
commit | d773b499e595a43b9b1ae449262dcf13cabf2d02 (patch) | |
tree | b1baeb12025a04f8316636b5d0ab18e30ceedb2c /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.c | 533 |
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 | |||
20 | iDeclareType(Glyph) | ||
21 | iDeclareTypeConstructionArgs(Glyph, iChar ch) | ||
22 | |||
23 | struct Impl_Glyph { | ||
24 | iHashNode node; | ||
25 | iRect rect; | ||
26 | int advance; | ||
27 | int dx, dy; | ||
28 | }; | ||
29 | |||
30 | void init_Glyph(iGlyph *d, iChar ch) { | ||
31 | d->node.key = ch; | ||
32 | d->rect = zero_Rect(); | ||
33 | d->advance = 0; | ||
34 | } | ||
35 | |||
36 | void deinit_Glyph(iGlyph *d) { | ||
37 | iUnused(d); | ||
38 | } | ||
39 | |||
40 | iChar char_Glyph(const iGlyph *d) { | ||
41 | return d->node.key; | ||
42 | } | ||
43 | |||
44 | iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) | ||
45 | |||
46 | /*-----------------------------------------------------------------------------------------------*/ | ||
47 | |||
48 | iDeclareType(Font) | ||
49 | |||
50 | struct Impl_Font { | ||
51 | iBlock * data; | ||
52 | stbtt_fontinfo font; | ||
53 | float scale; | ||
54 | int height; | ||
55 | int baseline; | ||
56 | iHash glyphs; | ||
57 | }; | ||
58 | |||
59 | static 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 | |||
71 | static 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 | |||
79 | iDeclareType(Text) | ||
80 | |||
81 | struct 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 | |||
91 | static iText text_; | ||
92 | |||
93 | void 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 | |||
134 | void 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 | |||
144 | static 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 | |||
154 | static iBool isSpecialChar_(iChar ch) { | ||
155 | return ch >= specialSymbol_Text && ch < 0x20; | ||
156 | } | ||
157 | |||
158 | static float symbolEmWidth_(int symbol) { | ||
159 | return 1.5f; | ||
160 | } | ||
161 | |||
162 | static float symbolAdvance_(int symbol) { | ||
163 | return 1.5f; | ||
164 | } | ||
165 | |||
166 | static int specialChar_(iChar ch) { | ||
167 | return ch - specialSymbol_Text; | ||
168 | } | ||
169 | |||
170 | iLocalDef SDL_Rect sdlRect_(const iRect rect) { | ||
171 | return (SDL_Rect){ rect.pos.x, rect.pos.y, rect.size.x, rect.size.y }; | ||
172 | } | ||
173 | |||
174 | static 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 | |||
205 | static 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 | |||
352 | static 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 | |||
363 | enum iRunMode { measure_RunMode, draw_RunMode, drawPermanentColor_RunMode }; | ||
364 | |||
365 | static 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 | |||
428 | int lineHeight_Text(int fontId) { | ||
429 | return text_.fonts[fontId].height; | ||
430 | } | ||
431 | |||
432 | iInt2 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 | |||
439 | iInt2 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 | |||
446 | iInt2 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 | |||
455 | static 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 | |||
467 | void 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 | |||
487 | void 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 | |||
500 | SDL_Texture *glyphCache_Text(void) { | ||
501 | return text_.cache; | ||
502 | } | ||
503 | |||
504 | /*-----------------------------------------------------------------------------------------------*/ | ||
505 | |||
506 | iDefineTypeConstructionArgs(TextBuf, (int font, const char *text), font, text) | ||
507 | |||
508 | void 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 | |||
522 | void deinit_TextBuf(iTextBuf *d) { | ||
523 | SDL_DestroyTexture(d->texture); | ||
524 | } | ||
525 | |||
526 | void 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 | } | ||