ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Font+Bitmap.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 * Permission is granted to anyone to use this software for any purpose,
8 * including commercial applications, and to alter it and redistribute it
9 * freely, subject to the following restrictions:
10 * 1. The origin of this software must not be misrepresented; you must not
11 * claim that you wrote the original software. If you use this software
12 * in a product, an acknowledgment in the product documentation would be
13 * appreciated but is not required.
14 * 2. Altered source versions must be plainly marked as such, and must not be
15 * misrepresented as being the original software.
16 */
17
18#include <assert.h>
19#include <limits.h>
20#include <math.h>
21#include <string.h>
22
23#include <Objectively/String.h>
24
25#include "Colors.h"
26#include "Font.h"
27#include "ImageAtlas.h"
28#include "Log.h"
29#include "Text.h"
30
31#define _Class _Font
32
33#pragma mark - Glyph blitting
34
38static SDL_Point cellOrigin(const FontBitmap *bitmap, Uint32 cell) {
39 return MakePoint((cell % FONT_BITMAP_COLUMNS) * bitmap->cellSize.w, (cell / FONT_BITMAP_COLUMNS) * bitmap->cellSize.h);
40}
41
46static void blit(SDL_Surface *src, SDL_Surface *dest, const SDL_Rect *rect) {
47
48 SDL_BlendMode blendMode;
49 SDL_GetSurfaceBlendMode(src, &blendMode);
50 SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
51
52 if (src->w == rect->w && src->h == rect->h) {
53 SDL_BlitSurface(src, NULL, dest, rect);
54 } else {
55 SDL_BlitSurfaceScaled(src, NULL, dest, rect, SDL_SCALEMODE_LINEAR);
56 }
57
58 SDL_SetSurfaceBlendMode(src, blendMode);
59}
60
64static void initGlyph(const FontBitmap *bitmap, TTF_Font *font, Uint32 codepoint, Uint32 cell) {
65
66 SDL_Surface *sheet = bitmap->surface;
67
68 if (!TTF_FontHasGlyph(font, codepoint)) {
69 return;
70 }
71
72 int minX = 0, maxX = 0;
73 if (!TTF_GetGlyphMetrics(font, codepoint, &minX, &maxX, NULL, NULL, NULL)) {
74 MVC_LogError("U+%04X: %s\n", codepoint, SDL_GetError());
75 return;
76 }
77
78 if (maxX <= minX) {
79 return;
80 }
81
82 SDL_Surface *glyph = TTF_RenderGlyph_Blended(font, codepoint, Colors.White);
83 if (glyph) {
84
85 // The glyph surface's left edge is the pen, with the glyph's own bearing already applied,
86 // except that SDL_ttf shifts a glyph with a negative bearing right so that its ink is not
87 // cut off. The cell's left edge is the pen plus the range's minimum bearing.
88 const SDL_Point origin = cellOrigin(bitmap, cell);
89 const SDL_Rect rect = MakeRect(origin.x - bitmap->bearing + min(minX, 0), origin.y, glyph->w, glyph->h);
90
91 // A glyph surface can exceed its cell, and must not overwrite its neighbours
92 const SDL_Rect clip = MakeRect(origin.x, origin.y, bitmap->cellSize.w, bitmap->cellSize.h);
93 SDL_SetSurfaceClipRect(sheet, &clip);
94
95 blit(glyph, sheet, &rect);
96
97 SDL_SetSurfaceClipRect(sheet, NULL);
98 SDL_DestroySurface(glyph);
99 } else {
100 MVC_LogDebug("U+%04X: %s\n", codepoint, SDL_GetError());
101 }
102}
103
104#pragma mark - Walking
105
113
114typedef struct {
116 Uint32 codepoint;
117 Uint32 cell;
118 SDL_Color color;
120} Token;
121
125static Uint32 cellForCodepoint(Uint32 codepoint) {
126
127 if (codepoint >= FONT_BITMAP_FIRST && codepoint < FONT_BITMAP_LAST) {
128 return codepoint - FONT_BITMAP_FIRST;
129 }
130
132}
133
137static void nextToken(const char **chars, const ImageAtlas *icons, Token *token) {
138
139 const char *p = *chars;
140
141 token->codepoint = 0;
142 token->cell = 0;
143 token->image = NULL;
144
145 while (*p == '\r') {
146 p++;
147 }
148
149 if (*p == '\0') {
150 token->type = TokenEnd;
151 *chars = p;
152 return;
153 }
154
155 if (*p == '\n') {
156 token->type = TokenNewline;
157 *chars = p + 1;
158 return;
159 }
160
161 if (p[0] == '^' && p[1] >= '0' && p[1] <= '9') {
162 token->type = TokenColor;
163 token->color = TextEscapeColors[p[1] - '0'];
164 *chars = p + 2;
165 return;
166 }
167
168 if (p[0] == '^' && p[1] == '^') {
169 token->type = TokenGlyph;
170 token->codepoint = '^';
171 token->cell = cellForCodepoint('^');
172 *chars = p + 2;
173 return;
174 }
175
176 if (*p == ':') {
177 const size_t length = MVC_IconEscapeLength(p, icons, &token->image);
178 if (length) {
179 token->type = TokenImage;
180 *chars = p + length;
181 return;
182 }
183 }
184
185 const Uint32 codepoint = SDL_StepUTF8(&p, NULL);
186
187 token->type = TokenGlyph;
188 token->codepoint = codepoint;
189 token->cell = cellForCodepoint(codepoint);
190 *chars = p;
191}
192
196static int tokenAdvance(const FontBitmap *bitmap, const Token *token) {
197
198 switch (token->type) {
199 case TokenGlyph:
200 return bitmap->advance;
201 case TokenImage:
202 // A square of the line height, in whole cells, so that the grid stays aligned
203 return ((bitmap->cellSize.h + bitmap->advance - 1) / bitmap->advance) * bitmap->advance;
204 default:
205 return 0;
206 }
207}
208
212static int wordAdvance(const FontBitmap *bitmap, const char *chars, const ImageAtlas *icons) {
213
214 int advance = 0;
215
216 while (true) {
217 const char *next = chars;
218
219 Token token;
220 nextToken(&next, icons, &token);
221
222 if (token.type == TokenEnd || token.type == TokenNewline) {
223 break;
224 }
225
226 if (token.type == TokenGlyph && token.codepoint == ' ') {
227 break;
228 }
229
230 advance += tokenAdvance(bitmap, &token);
231 chars = next;
232 }
233
234 return advance;
235}
236
240typedef void (*TokenVisitor)(const FontBitmap *bitmap, const Token *token, int x, int y, ident data);
241
246static void walk(const FontBitmap *bitmap, const char *chars, int wrapWidth, SDL_Color color, const ImageAtlas *icons,
247 TokenVisitor visitor, ident data, int *w, int *h) {
248
249 int x = 0, y = 0, maxX = 0;
250 bool lineHasContent = false;
251
252 const int overhang = max(0, bitmap->bearing + bitmap->cellSize.w - bitmap->advance);
253
254 Token token = { .type = TokenEnd, .color = color };
255
256 while (true) {
257 nextToken(&chars, icons, &token);
258
259 if (token.type == TokenEnd) {
260 break;
261 }
262
263 if (token.type == TokenNewline) {
264 x = 0;
265 y += bitmap->cellSize.h;
266 lineHasContent = false;
267 continue;
268 }
269
270 if (token.type == TokenColor) {
271 color = token.color;
272 continue;
273 }
274
275 if (wrapWidth && token.type == TokenGlyph && token.codepoint == ' ' && x > 0) {
276 if (x + bitmap->advance + wordAdvance(bitmap, chars, icons) > wrapWidth) {
277 x = 0;
278 y += bitmap->cellSize.h;
279 lineHasContent = false;
280 continue;
281 }
282 }
283
284 const int advance = tokenAdvance(bitmap, &token);
285
286 if (wrapWidth && x > 0 && x + advance > wrapWidth) {
287 x = 0;
288 y += bitmap->cellSize.h;
289 lineHasContent = false;
290 }
291
292 if (visitor) {
293 token.color = token.type == TokenImage ? Colors.White : color;
294 visitor(bitmap, &token, x, y, data);
295 }
296
297 x += advance;
298 lineHasContent = true;
299 // Ink past the pen: a glyph's cell overhang, or the bearing an icon shares with the glyph
300 // column, less what centering in its slot gives back
301 int extent = 0;
302 if (token.type == TokenGlyph) {
303 extent = overhang;
304 } else if (token.type == TokenImage) {
305 const int side = bitmap->cellSize.h;
306 extent = max(0, bitmap->bearing + (advance - side) / 2 + side - advance);
307 }
308
309 maxX = max(maxX, x + extent);
310 }
311
312 if (w) {
313 *w = maxX;
314 }
315 if (h) {
316 *h = y + (lineHasContent ? bitmap->cellSize.h : 0);
317 }
318}
319
320#pragma mark - FontBitmap
321
323
324 assert(bitmap);
325 assert(font);
326
327 if (!TTF_FontIsFixedWidth(font->font)) {
328 String *name = $(font, name);
329 MVC_LogWarn("%s is not fixed-width\n", name->chars);
330 release(name);
331 return false;
332 }
333
334 int minX = INT_MAX, maxX = INT_MIN, advance = 0;
335
336 for (Uint32 codepoint = FONT_BITMAP_FIRST; codepoint < FONT_BITMAP_LAST; codepoint++) {
337
338 if (TTF_FontHasGlyph(font->font, codepoint)) {
339 int glyphMinX = 0, glyphMaxX = 0, glyphAdvance = 0;
340 if (!TTF_GetGlyphMetrics(font->font, codepoint, &glyphMinX, &glyphMaxX, NULL, NULL, &glyphAdvance)) {
341 continue;
342 }
343
344 minX = min(minX, glyphMinX);
345 maxX = max(maxX, max(glyphMaxX, glyphAdvance));
346 advance = max(advance, glyphAdvance);
347 }
348 }
349
350 if (maxX <= minX || advance == 0) {
351 String *name = $(font, name);
352 MVC_LogWarn("%s has no glyphs in U+%04X-U+%04X\n", name->chars, FONT_BITMAP_FIRST, FONT_BITMAP_LAST - 1);
353 release(name);
354 return false;
355 }
356
357 bitmap->advance = advance;
358 bitmap->bearing = minX;
359 bitmap->cellSize = MakeSize(maxX - minX, TTF_GetFontHeight(font->font));
360
361 const int rows = FONT_BITMAP_COUNT / FONT_BITMAP_COLUMNS;
362
363 bitmap->surface = SDL_CreateSurface(FONT_BITMAP_COLUMNS * bitmap->cellSize.w, rows * bitmap->cellSize.h, SDL_PIXELFORMAT_RGBA32);
364 assert(bitmap->surface);
365
366 SDL_FillSurfaceRect(bitmap->surface, NULL, 0);
367
368 for (Uint32 i = 0; i < FONT_BITMAP_COUNT; i++) {
369 initGlyph(bitmap, font->font, FONT_BITMAP_FIRST + i, i);
370 }
371
372 return true;
373}
374
376
377 assert(bitmap);
378
379 release(bitmap->texture);
380
381 if (bitmap->surface) {
382 SDL_DestroySurface(bitmap->surface);
383 }
384}
385
391
392 assert(self);
393
394 self->bitmap.texture = release(self->bitmap.texture);
395}
396
397typedef struct {
399 RenderDevice *device;
400 Texture *texture;
401 SDL_FPoint origin;
402 float scale;
404
408static void renderToken(const FontBitmap *bitmap, const Token *token, int x, int y, ident data) {
409
410 const RenderContext *context = data;
411
412 if (token->type == TokenImage) {
413 ImageAtlas *atlas = token->image->atlas;
414 if (atlas == NULL) {
415 return;
416 }
417
418 // A square of the line height, centered in its whole-cell slot, from the icon atlas
419 const int side = bitmap->cellSize.h;
420 const int slot = tokenAdvance(bitmap, token);
421
422 const SDL_FRect dest = {
423 (context->origin.x + x + bitmap->bearing + (slot - side) / 2) / context->scale,
424 (context->origin.y + y) / context->scale,
425 side / context->scale,
426 side / context->scale
427 };
428
429 Texture *texture = $(atlas, texture, context->device);
430 $(context->renderer, drawTextureRegion, texture, &token->image->rect, &dest, &token->color);
431 return;
432 }
433
434 const SDL_Point origin = cellOrigin(bitmap, token->cell);
435 const SDL_Rect src = MakeRect(origin.x, origin.y, bitmap->cellSize.w, bitmap->cellSize.h);
436
437 // Positioned in whole texels so that the sheet is sampled 1:1, then scaled to logical
438 const SDL_FRect dest = {
439 (context->origin.x + x + bitmap->bearing) / context->scale,
440 (context->origin.y + y) / context->scale,
441 src.w / context->scale,
442 src.h / context->scale
443 };
444
445 $(context->renderer, drawTextureRegion, context->texture, &src, &dest, &token->color);
446}
447
452void renderCharactersBitmap(Font *self, const Renderer *renderer, const char *chars, SDL_Color color, int wrapWidth, const SDL_Point *origin, const ImageAtlas *icons) {
453
454 assert(self);
455 assert(renderer);
456 assert(origin);
457
458 FontBitmap *bitmap = &self->bitmap;
459 assert(bitmap->surface);
460
461 if (chars == NULL) {
462 return;
463 }
464
465 if (bitmap->texture == NULL) {
466 bitmap->texture = $(renderer->device, createTextureFromSurface, bitmap->surface, SDL_GPU_TEXTUREUSAGE_SAMPLER, false);
467 assert(bitmap->texture);
468 }
469
470 RenderContext context = {
471 .renderer = renderer,
472 .device = renderer->device,
473 .texture = bitmap->texture,
474 .origin = { roundf(origin->x * self->pixelDensity), roundf(origin->y * self->pixelDensity) },
475 .scale = self->pixelDensity,
476 };
477
478 walk(bitmap, chars, (int) (wrapWidth * context.scale), color, icons, renderToken, &context, NULL, NULL);
479}
480
485void sizeCharactersBitmap(const Font *self, const char *chars, int wrapWidth, const ImageAtlas *icons, int *w, int *h) {
486
487 assert(self);
488
489 const FontBitmap *bitmap = &self->bitmap;
490 assert(bitmap->surface);
491
492 int texelsW = 0, texelsH = 0;
493
494 if (chars) {
495 walk(bitmap, chars, (int) (wrapWidth * self->pixelDensity), Colors.White, icons, NULL, NULL, &texelsW, &texelsH);
496 }
497
498 if (w) {
499 *w = (int) ceilf(texelsW / self->pixelDensity);
500 }
501 if (h) {
502 *h = (int) ceilf(texelsH / self->pixelDensity);
503 }
504}
505
506#undef _Class
W3C Color constants.
static void blit(SDL_Surface *src, SDL_Surface *dest, const SDL_Rect *rect)
Copies src into dest at rect, scaling if necessary, without alpha blending, so that glyph coverage la...
Definition Font+Bitmap.c:46
static int wordAdvance(const FontBitmap *bitmap, const char *chars, const ImageAtlas *icons)
The advance of the word starting at chars, up to the next space, newline or end.
static void initGlyph(const FontBitmap *bitmap, TTF_Font *font, Uint32 codepoint, Uint32 cell)
Rasterizes codepoint into the given cell, with its bearing baked in.
Definition Font+Bitmap.c:64
void renderCharactersBitmap(Font *self, const Renderer *renderer, const char *chars, SDL_Color color, int wrapWidth, const SDL_Point *origin, const ImageAtlas *icons)
Implements Font::renderBitmapCharacters.
void(* TokenVisitor)(const FontBitmap *bitmap, const Token *token, int x, int y, ident data)
Receives each drawable token with the pen position of its cell, in texels.
void deallocBitmap(FontBitmap *bitmap)
Releases the resources held by bitmap. Safe to call on a zeroed FontBitmap.
static void renderToken(const FontBitmap *bitmap, const Token *token, int x, int y, ident data)
TokenVisitor for Font::renderBitmapCharacters.
static int tokenAdvance(const FontBitmap *bitmap, const Token *token)
The pen advance of the given token, in texels.
static void walk(const FontBitmap *bitmap, const char *chars, int wrapWidth, SDL_Color color, const ImageAtlas *icons, TokenVisitor visitor, ident data, int *w, int *h)
Walks chars, wrapping at word boundaries when wrapWidth (in texels) is non-zero, and reports the exte...
static SDL_Point cellOrigin(const FontBitmap *bitmap, Uint32 cell)
The top-left texel of the given cell within the grid surface.
Definition Font+Bitmap.c:38
void renderDeviceWillResetBitmap(Font *self)
Implements Font::renderDeviceWillReset.
TokenType
@ TokenColor
@ TokenImage
@ TokenEnd
@ TokenGlyph
@ TokenNewline
void sizeCharactersBitmap(const Font *self, const char *chars, int wrapWidth, const ImageAtlas *icons, int *w, int *h)
Implements Font::sizeBitmapCharacters.
static void nextToken(const char **chars, const ImageAtlas *icons, Token *token)
Advances *chars past the next token, describing it in token.
bool initBitmap(FontBitmap *bitmap, Font *font)
Bakes FONT_BITMAP_COUNT codepoints of font, from FONT_BITMAP_FIRST, into a glyph sheet,...
static Uint32 cellForCodepoint(Uint32 codepoint)
Resolves codepoint to a cell: FONT_BITMAP_REPLACEMENT's for one outside the baked range.
#define FONT_BITMAP_REPLACEMENT
The codepoint drawn in place of one outside the baked range.
Definition Font+Bitmap.h:55
#define FONT_BITMAP_COUNT
The codepoint count Font bakes into its bitmap.
Definition Font+Bitmap.h:40
#define FONT_BITMAP_FIRST
The first codepoint Font bakes into its bitmap.
Definition Font+Bitmap.h:31
#define FONT_BITMAP_LAST
One past the last baked codepoint in a Font bitmap (exclusive upper bound).
Definition Font+Bitmap.h:45
#define FONT_BITMAP_COLUMNS
The number of cells per row of the glyph sheet: FONT_BITMAP_COUNT cells make a square grid of this ma...
Definition Font+Bitmap.h:50
static String * name(const Font *self)
Definition Font.c:303
TrueType fonts.
static Texture * texture(ImageAtlas *self, RenderDevice *device)
Definition ImageAtlas.c:289
Packs many Images into one sheet, backed by one Texture.
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
#define MVC_LogDebug(fmt,...)
Definition Log.h:48
#define MVC_LogError(fmt,...)
Definition Log.h:57
static void drawTextureRegion(const Renderer *self, Texture *texture, const SDL_Rect *src, const SDL_FRect *dest, const SDL_Color *color)
Definition Renderer.c:497
SDL_Color TextEscapeColors[]
Definition Text.c:41
size_t MVC_IconEscapeLength(const char *chars, const ImageAtlas *icons, AtlasImage **image)
Parses an icon escape at the start of chars: :name:, where name is one or more of [A-Za-z0-9_-] and u...
Definition Text.c:104
Text rendered with TrueType fonts.
static ImageAtlas * icons(Theme *self)
Definition Theme.c:144
static Font * font(Theme *self, const FontAttributes *attributes, float pixelDensity)
Definition Theme.c:108
An Image packed into an ImageAtlas.
Definition AtlasImage.h:41
SDL_Rect rect
This AtlasImage's rectangle within the atlas sheet, in texels.
Definition AtlasImage.h:66
ImageAtlas * atlas
The ImageAtlas this AtlasImage belongs to, or NULL once that atlas has been released,...
Definition AtlasImage.h:59
W3C Color constants.
Definition Colors.h:37
SDL_Color White
Definition Colors.h:185
A fixed-width Font's glyphs, baked into one sheet, drawn as one quad per glyph.
Definition Font+Bitmap.h:82
int bearing
The horizontal offset from the pen to a cell's left edge, in texels.
Definition Font+Bitmap.h:92
SDL_Size cellSize
The size of one cell, in texels.
Definition Font+Bitmap.h:97
int advance
The pen advance per glyph, in texels.
Definition Font+Bitmap.h:86
SDL_Surface * surface
The glyph sheet: a grid of FONT_BITMAP_COLUMNS by FONT_BITMAP_COLUMNS cells of cellSize,...
TrueType fonts.
Definition Font.h:88
float pixelDensity
The display pixel density scale, greater than 1.0 on high-density displays.
Definition Font.h:134
FontBitmap bitmap
The glyphs of a fixed-width Font, baked into one sheet; bitmap.surface is NULL for a proportional Fon...
Definition Font.h:107
TTF_Font * font
The backing font.
Definition Font.h:122
Packs many Images into one sheet, backed by one Texture.
Definition ImageAtlas.h:49
RenderDevice * device
SDL_FPoint origin
Texture * texture
const Renderer * renderer
Renderer extends Object with ObjectivelyMVC's UI rendering layer.
Definition Renderer.h:75
RenderDevice * device
The backing RenderDevice.
Definition Renderer.h:97
Uint32 codepoint
SDL_Color color
TokenType type
Uint32 cell
AtlasImage * image