ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Font.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 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <math.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include <Objectively/Hash.h>
31#include <Objectively/Array.h>
32#include <Objectively/String.h>
33
34#include "Font.h"
35#include "Log.h"
36#include "View.h"
37#include "Window.h"
38
39#include "../Assets/coda.ttf.h"
40#include "../Assets/shareTechMono.ttf.h"
41
42const EnumName FontStyleNames[] = MakeEnumNames(
43 MakeEnumAlias(FontStyleRegular, regular),
44 MakeEnumAlias(FontStyleBold, bold),
45 MakeEnumAlias(FontStyleItalic, italic),
46 MakeEnumAlias(FontStyleUnderline, underline),
47 MakeEnumAlias(FontStyleStrikeThrough, strikethrough)
48);
49
50#define _Class _Font
51
52#pragma mark - Object
53
57static void dealloc(Object *self) {
58
59 Font *this = (Font *) self;
60
61 deallocBitmap(&this->bitmap);
62
63 TTF_CloseFont(this->font);
64
65 free(this->family);
66 free(this->iconPlaceholder);
67
68 release(this->data);
69
70 super(Object, self, dealloc);
71}
72
76static int hash(const Object *self) {
77
78 Font *this = (Font *) self;
79
80 int hash = HASH_SEED;
81 hash = HashForCString(hash, this->family);
82 hash = HashForInteger(hash, this->size);
83 hash = HashForInteger(hash, this->style);
84 hash = HashForInteger(hash, this->renderSize);
85
86 return hash;
87}
88
92static bool isEqual(const Object *self, const Object *other) {
93
94 if (super(Object, self, isEqual, other)) {
95 return true;
96 }
97
98 if (other && $(other, isKindOfClass, _Font())) {
99
100 const Font *this = (Font *) self;
101 const Font *that = (Font *) other;
102
103 if (!strcmp(this->family, that->family) &&
104 this->size == that->size &&
105 this->style == that->style &&
106 this->renderSize == that->renderSize) {
107 return true;
108 }
109 }
110
111 return false;
112}
113
114#pragma mark - Font
115
116static Dictionary *_cache;
117
122static void cacheFont(Data *data, const char *family) {
123 $(_cache, setObjectForKeyPath, data, family);
124}
125
130static void normalizeAttributes(FontAttributes *attributes) {
131
132 if (attributes->family == NULL) {
133 attributes->family = DEFAULT_FONT_FAMILY;
134 }
135
136 if (attributes->size < 1) {
137 attributes->size = DEFAULT_FONT_SIZE;
138 }
139
140 if (attributes->style < FontStyleRegular || attributes->style > FontStyleStrikeThrough) {
141 attributes->style = DEFAULT_FONT_STYLE;
142 }
143}
144
149static void clearCache(void) {
150 $(_cache, removeAllObjects);
151}
152
154
159static Font *defaultFont(void) {
160 static Once once;
161
162 const FontAttributes attrs = {
164 };
165
166 do_once(&once, {
167 _defaultFont = $$(Font, fontWithAttributes, &attrs, 1.f);
168 assert(_defaultFont);
169 });
170
171 return _defaultFont;
172}
173
175
181 static Once once;
182
183 const FontAttributes attrs = {
185 };
186
187 do_once(&once, {
189 assert(_defaultMonospaceFont);
190 });
191
193}
194
199static Font *fontWithAttributes(const FontAttributes *attributes, float pixelDensity) {
200
201 FontAttributes normalized = *attributes;
202 normalizeAttributes(&normalized);
203
204 if (pixelDensity < 1.f) {
205 pixelDensity = 1.f;
206 }
207
208 Data *data = $((Dictionary *) _cache, objectForKeyPath, normalized.family);
209 if (data) {
210 Font *font = $(alloc(Font), initWithData, data, &normalized, pixelDensity);
211 assert(font);
212
213 return font;
214 }
215
216 MVC_LogWarn("%s-%d-%d not found\n", normalized.family, normalized.size, normalized.style);
217 return retain($$(Font, defaultFont));
218}
219
224static Font *fontWithName(const char *name, float pixelDensity) {
225
226 assert(name);
227
228 char *chars = strdup(name);
229 assert(chars);
230
231 Font *font = NULL;
232
233 char *styleToken = strrchr(chars, '-');
234 if (styleToken) {
235 *styleToken++ = '\0';
236
237 char *sizeToken = strrchr(chars, '-');
238 if (sizeToken) {
239 *sizeToken++ = '\0';
240
241 const FontAttributes attributes = {
242 chars, (int) strtol(sizeToken, NULL, 10), valueof(FontStyleNames, styleToken)
243 };
244
245 font = $$(Font, fontWithAttributes, &attributes, pixelDensity);
246 }
247 }
248
249 free(chars);
250
251 if (font == NULL) {
252 MVC_LogWarn("%s is not a valid Font name\n", name);
253 font = retain($$(Font, defaultFont));
254 }
255
256 return font;
257}
258
263static Font *initWithData(Font *self, Data *data, const FontAttributes *attributes, float pixelDensity) {
264
265 self = (Font *) super(Object, self, init);
266 if (self) {
267
268 self->data = retain(data);
269 assert(self->data);
270
271 self->family = strdup(attributes->family);
272 assert(self->family);
273
274 self->size = attributes->size;
275 assert(self->size);
276
277 self->style = attributes->style;
278
279 self->pixelDensity = pixelDensity;
280 self->renderSize = self->size * self->pixelDensity;
281
282 SDL_IOStream *buffer = SDL_IOFromConstMem(self->data->bytes, (int) self->data->length);
283 assert(buffer);
284
285 self->font = TTF_OpenFontIO(buffer, 1, self->renderSize);
286 assert(self->font);
287
288 TTF_SetFontStyle(self->font, self->style);
289 TTF_SetFontHinting(self->font, TTF_HINTING_LIGHT_SUBPIXEL);
290
291 if (TTF_FontIsFixedWidth(self->font)) {
292 initBitmap(&self->bitmap, self);
293 }
294 }
295
296 return self;
297}
298
303static String *name(const Font *self) {
304 const FontAttributes attributes = { self->family, self->size, self->style };
305 return $$(Font, nameWithAttributes, &attributes);
306}
307
312static String *nameWithAttributes(const FontAttributes *attributes) {
313
314 FontAttributes normalized = *attributes;
315 normalizeAttributes(&normalized);
316
317 for (const EnumName *en = FontStyleNames; en->name; en++) {
318 if (en->value == normalized.style) {
319 return str("%s-%d-%s", normalized.family, normalized.size, en->alias ?: en->name);
320 }
321 }
322
323 return str("%s-%d-%d", normalized.family, normalized.size, normalized.style);
324}
325
330static SDL_Surface *renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth) {
331
332 // Always the wrapping renderer: with a width of 0 it breaks on newlines only, which is what
333 // Font::sizeCharacters and the TTF_Text layout Text builds its color runs from both assume,
334 // whereas TTF_RenderText_Blended draws a newline as a missing glyph on a single line
335 SDL_Surface *surface = TTF_RenderText_Blended_Wrapped(self->font, chars, 0, color, wrapWidth * self->pixelDensity);
336
337 SDL_Surface *converted = NULL;
338 if (surface) {
339 converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
340 SDL_DestroySurface(surface);
341 } else {
342 MVC_LogError("%s\n", SDL_GetError());
343 }
344
345 return converted;
346}
347
352static void sizeCharacters(const Font *self, const char *chars, int *w, int *h) {
353
354 if (w) {
355 *w = 0;
356 }
357 if (h) {
358 *h = 0;
359 }
360
361 if (chars) {
362 char *lines = strdup(chars);
363
364 const int font_h = TTF_GetFontHeight(self->font);
365
366 for (char *line = strtok(lines, "\n\r"); line; line = strtok(NULL, "\n\r")) {
367
368 if (w) {
369
370 int lineWidth;
371 TTF_GetStringSize(self->font, line, 0, &lineWidth, NULL);
372
373 // TTF_GetStringSize sums glyph advances, but the final glyph's ink can extend past its
374 // own advance (common with bold or large faces). Check the final glyph's bounds and
375 // ensure we take the larger value.
376 const char *end = line + strlen(line);
377 const Uint32 ch = SDL_StepBackUTF8(line, &end);
378 if (ch) {
379 int maxX, advance;
380 if (TTF_GetGlyphMetrics(self->font, ch, NULL, &maxX, NULL, NULL, &advance)) {
381 if (maxX > advance) {
382 lineWidth += maxX - advance;
383 }
384 }
385 }
386
387 *w = max(*w, lineWidth);
388 }
389 if (h) {
390 *h += font_h;
391 }
392 }
393 free(lines);
394
395 if (w) {
396 *w = ceilf(*w / self->pixelDensity);
397 }
398 if (h) {
399 *h = ceilf(*h / self->pixelDensity);
400 }
401 }
402}
403
404#pragma mark - Class lifecycle
405
409static void initialize(Class *clazz) {
410
411 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
412 ((ObjectInterface *) clazz->interface)->hash = hash;
413 ((ObjectInterface *) clazz->interface)->isEqual = isEqual;
414
415 ((FontInterface *) clazz->interface)->cacheFont = cacheFont;
416 ((FontInterface *) clazz->interface)->clearCache = clearCache;
417 ((FontInterface *) clazz->interface)->defaultFont = defaultFont;
418 ((FontInterface *) clazz->interface)->defaultMonospaceFont = defaultMonospaceFont;
419 ((FontInterface *) clazz->interface)->fontWithAttributes = fontWithAttributes;
420 ((FontInterface *) clazz->interface)->fontWithName = fontWithName;
421 ((FontInterface *) clazz->interface)->initWithData = initWithData;
422 ((FontInterface *) clazz->interface)->name = name;
423 ((FontInterface *) clazz->interface)->nameWithAttributes = nameWithAttributes;
424 ((FontInterface *) clazz->interface)->renderBitmapCharacters = renderCharactersBitmap;
425 ((FontInterface *) clazz->interface)->renderCharacters = renderCharacters;
426 ((FontInterface *) clazz->interface)->renderDeviceWillReset = renderDeviceWillResetBitmap;
427 ((FontInterface *) clazz->interface)->sizeBitmapCharacters = sizeCharactersBitmap;
428 ((FontInterface *) clazz->interface)->sizeCharacters = sizeCharacters;
429
430 const bool init = TTF_Init();
431 assert(init);
432 (void) init;
433
434 _cache = $$(Dictionary, dictionary);
435 assert(_cache);
436
437 Data *coda = $(alloc(Data), initWithConstMemory, coda_ttf, coda_ttf_len - 1);
438 assert(coda);
440 release(coda);
441
442 Data *shareTechMono = $(alloc(Data), initWithConstMemory, shareTechMono_ttf, shareTechMono_ttf_len - 1);
443 assert(shareTechMono);
445 release(shareTechMono);
446}
447
451static void destroy(Class *clazz) {
452
453 release(_cache);
454 release(_defaultFont);
455 release(_defaultMonospaceFont);
456
457 TTF_Quit();
458}
459
464Class *_Font(void) {
465 static Class *clazz;
466 static Once once;
467
468 do_once(&once, {
469 clazz = _initialize(&(const ClassDef) {
470 .name = "Font",
471 .superclass = _Object(),
472 .instanceSize = sizeof(Font),
473 .interfaceSize = sizeof(FontInterface),
474 .initialize = initialize,
475 .destroy = destroy,
476 });
477 });
478
479 return clazz;
480}
481
482#undef _Class
static View * init(View *self)
Definition Box.c:67
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 deallocBitmap(FontBitmap *bitmap)
Releases the resources held by bitmap. Safe to call on a zeroed FontBitmap.
void renderDeviceWillResetBitmap(Font *self)
Implements Font::renderDeviceWillReset.
void sizeCharactersBitmap(const Font *self, const char *chars, int wrapWidth, const ImageAtlas *icons, int *w, int *h)
Implements Font::sizeBitmapCharacters.
bool initBitmap(FontBitmap *bitmap, Font *font)
Bakes FONT_BITMAP_COUNT codepoints of font, from FONT_BITMAP_FIRST, into a glyph sheet,...
static void destroy(Class *clazz)
Definition Font.c:451
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition Font.c:352
static SDL_Surface * renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth)
Definition Font.c:330
static void clearCache(void)
Definition Font.c:149
static Font * _defaultMonospaceFont
Definition Font.c:174
static String * nameWithAttributes(const FontAttributes *attributes)
Definition Font.c:312
static Font * fontWithName(const char *name, float pixelDensity)
Definition Font.c:224
static Font * defaultMonospaceFont(void)
Definition Font.c:180
static bool isEqual(const Object *self, const Object *other)
Definition Font.c:92
static void normalizeAttributes(FontAttributes *attributes)
Normalizes Font attributes to their defaults, in one place, so that Font::nameWithAttributes and Font...
Definition Font.c:130
Class * _Font(void)
Definition Font.c:464
static void dealloc(Object *self)
Definition Font.c:57
static Font * fontWithAttributes(const FontAttributes *attributes, float pixelDensity)
Definition Font.c:199
static Dictionary * _cache
Definition Font.c:116
const EnumName FontStyleNames[]
Definition Font.c:42
static void initialize(Class *clazz)
Definition Font.c:409
static void cacheFont(Data *data, const char *family)
Definition Font.c:122
static Font * initWithData(Font *self, Data *data, const FontAttributes *attributes, float pixelDensity)
Definition Font.c:263
static String * name(const Font *self)
Definition Font.c:303
static Font * _defaultFont
Definition Font.c:153
static int hash(const Object *self)
Definition Font.c:76
static Font * defaultFont(void)
Definition Font.c:159
TrueType fonts.
#define DEFAULT_MONOSPACE_FONT_FAMILY
Definition Font.h:40
#define DEFAULT_FONT_STYLE
Definition Font.h:38
@ FontStyleUnderline
Definition Font.h:54
@ FontStyleBold
Definition Font.h:52
@ FontStyleStrikeThrough
Definition Font.h:55
@ FontStyleRegular
Definition Font.h:51
@ FontStyleItalic
Definition Font.h:53
#define DEFAULT_FONT_FAMILY
Definition Font.h:36
#define DEFAULT_FONT_SIZE
Definition Font.h:37
static SDL_Size size(const Image *self)
Definition Image.c:416
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
#define MVC_LogError(fmt,...)
Definition Log.h:57
static const char * iconPlaceholder(const Font *font)
The placeholder an icon occupies in font's layout: a codepoint that is not a line-break opportunity,...
Definition Text.c:171
static Font * font(Theme *self, const FontAttributes *attributes, float pixelDensity)
Definition Theme.c:108
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
The family, size and style attributes that identify a Font, independent of the pixel density at which...
Definition Font.h:64
const char * family
The family, or NULL for the default.
Definition Font.h:68
int style
The FontStyle, or -1 for the default.
Definition Font.h:78
int size
The point size, or 0 for the default.
Definition Font.h:73
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
Font * defaultMonospaceFont(void)
Definition Font.c:180
int renderSize
The render size, adjusted for pixel density.
Definition Font.h:139
char * family
The family name.
Definition Font.h:117
Font * initWithData(Font *self, Data *data, const FontAttributes *attributes, float pixelDensity)
Initializes this Font with the given TTF Data and attributes.
Definition Font.c:263
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
Font * fontWithName(const char *name, float pixelDensity)
Resolves a Font from a name in the format produced by Font::name.
Definition Font.c:224
int size
The point size.
Definition Font.h:144
Font * fontWithAttributes(const FontAttributes *attributes, float pixelDensity)
Resolves a new Font with the given attributes from the cached font Data.
Definition Font.c:199
TTF_Font * font
The backing font.
Definition Font.h:122
String * name(const Font *self)
Definition Font.c:303
Data * data
The raw font data.
Definition Font.h:112
int style
The style.
Definition Font.h:149