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
41const EnumName FontStyleNames[] = MakeEnumNames(
42 MakeEnumAlias(FontStyleRegular, regular),
43 MakeEnumAlias(FontStyleBold, bold),
44 MakeEnumAlias(FontStyleItalic, italic),
45 MakeEnumAlias(FontStyleUnderline, underline),
46 MakeEnumAlias(FontStyleStrikeThrough, strikethrough)
47);
48
49#define _Class _Font
50
51#pragma mark - Object
52
56static void dealloc(Object *self) {
57
58 Font *this = (Font *) self;
59
60 free(this->family);
61
62 release(this->data);
63
64 TTF_CloseFont(this->font);
65
66 super(Object, self, dealloc);
67}
68
72static int hash(const Object *self) {
73
74 Font *this = (Font *) self;
75
76 int hash = HASH_SEED;
77 hash = HashForCString(hash, this->family);
78 hash = HashForInteger(hash, this->size);
79 hash = HashForInteger(hash, this->style);
80 hash = HashForInteger(hash, this->renderSize);
81
82 return hash;
83}
84
88static bool isEqual(const Object *self, const Object *other) {
89
90 if (super(Object, self, isEqual, other)) {
91 return true;
92 }
93
94 if (other && $(other, isKindOfClass, _Font())) {
95
96 const Font *this = (Font *) self;
97 const Font *that = (Font *) other;
98
99 if (!strcmp(this->family, that->family) &&
100 this->size == that->size &&
101 this->style == that->style &&
102 this->renderSize == that->renderSize) {
103 return true;
104 }
105 }
106
107 return false;
108}
109
110#pragma mark - Font
111
112static Dictionary *_cache;
113static Array *_fonts;
114
119static void cacheFont(Data *data, const char *family) {
120 $(_cache, setObjectForKeyPath, data, family);
121}
122
127static Font *cachedFont(const char *family, int size, int style) {
128
129 if (family == NULL) {
130 family = DEFAULT_FONT_FAMILY;
131 }
132 if (size < 1) {
134 }
135 if (style < FontStyleRegular || style > FontStyleStrikeThrough) {
136 style = DEFAULT_FONT_STYLE;
137 }
138
139 const Array *fonts = (Array *) _fonts;
140 for (size_t i = 0; i < fonts->count; i++) {
141
142 Font *font = $(fonts, objectAtIndex, i);
143
144 if (!strcmp(font->family, family) &&
145 font->size == size &&
146 font->style == style) {
147 return font;
148 }
149 }
150
151 Data *data = $((Dictionary *) _cache, objectForKeyPath, family);
152 if (data) {
153 Font *font = $(alloc(Font), initWithData, data, family, size, style);
154 assert(font);
155
156 $(_fonts, addObject, font);
157 release(font);
158
159 return font;
160 }
161
162 MVC_LogWarn("%s-%d-%d not found\n", family, size, style);
163 return $$(Font, defaultFont);
164}
165
170static void clearCache(void) {
171 $(_cache, removeAllObjects);
172}
173
178static Font *defaultFont(void) {
179 static Once once;
180
181 do_once(&once, {
182 Data *data = $(alloc(Data), initWithConstMemory, coda_ttf, coda_ttf_len - 1);
183 assert(data);
184
186
187 release(data);
188 });
189
191}
192
197static Font *initWithData(Font *self, Data *data, const char *family, int size, int style) {
198
199 self = (Font *) super(Object, self, init);
200 if (self) {
201
202 self->data = retain(data);
203 assert(self->data);
204
205 self->family = strdup(family);
206 assert(self->family);
207
208 self->size = size;
209 assert(self->size);
210
211 self->style = style;
212 self->scale = 1.0f;
213
214 $(self, renderDeviceDidReset);
215 }
216
217 return self;
218}
219
224static SDL_Surface *renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth) {
225
226 SDL_Surface *surface;
227 if (wrapWidth) {
228 surface = TTF_RenderText_Blended_Wrapped(self->font, chars, 0, color, wrapWidth * self->scale);
229 } else {
230 surface = TTF_RenderText_Blended(self->font, chars, 0, color);
231 }
232
233 SDL_Surface *converted = NULL;
234 if (surface) {
235 converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
236 SDL_DestroySurface(surface);
237 } else {
238 MVC_LogError("%s\n", SDL_GetError());
239 }
240
241 return converted;
242}
243
248static void renderDeviceDidReset(Font *self) {
249
250 const int renderSize = self->size * self->scale;
251 if (renderSize != self->renderSize) {
252
253 self->renderSize = renderSize;
254
255 if (self->font) {
256 TTF_CloseFont(self->font);
257 }
258
259 SDL_IOStream *buffer = SDL_IOFromConstMem(self->data->bytes, (int) self->data->length);
260 assert(buffer);
261
262 self->font = TTF_OpenFontIO(buffer, 1, self->renderSize);
263 assert(self->font);
264
265 TTF_SetFontStyle(self->font, self->style);
266 TTF_SetFontHinting(self->font, TTF_HINTING_LIGHT_SUBPIXEL);
267 }
268}
269
274static void sizeCharacters(const Font *self, const char *chars, int *w, int *h) {
275
276 if (w) {
277 *w = 0;
278 }
279 if (h) {
280 *h = 0;
281 }
282
283 if (chars) {
284 char *lines = strdup(chars);
285
286 const int font_h = TTF_GetFontHeight(self->font);
287
288 for (char *line = strtok(lines, "\n\r"); line; line = strtok(NULL, "\n\r")) {
289
290 if (w) {
291
292 // A little ghetto whitespace padding to ensure TTF doesn't truncate words 🫠
293
294 const size_t len = strlen(line) + 2;
295 char buf[len];
296
297 snprintf(buf, len, "%s ", line);
298
299 int line_w;
300
301 TTF_GetStringSize(self->font, buf, 0, &line_w, NULL);
302 *w = max(*w, line_w);
303 }
304 if (h) {
305 *h += font_h;
306 }
307 }
308 free(lines);
309
310 if (w) {
311 *w = ceilf(*w / self->scale);
312 }
313 if (h) {
314 *h = ceilf(*h / self->scale);
315 }
316 }
317}
318
319#pragma mark - Class lifecycle
320
324static void initialize(Class *clazz) {
325
326 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
327 ((ObjectInterface *) clazz->interface)->hash = hash;
328 ((ObjectInterface *) clazz->interface)->isEqual = isEqual;
329
330 ((FontInterface *) clazz->interface)->cachedFont = cachedFont;
331 ((FontInterface *) clazz->interface)->cacheFont = cacheFont;
332 ((FontInterface *) clazz->interface)->clearCache = clearCache;
333 ((FontInterface *) clazz->interface)->defaultFont = defaultFont;
334 ((FontInterface *) clazz->interface)->initWithData = initWithData;
335 ((FontInterface *) clazz->interface)->renderCharacters = renderCharacters;
336 ((FontInterface *) clazz->interface)->renderDeviceDidReset = renderDeviceDidReset;
337 ((FontInterface *) clazz->interface)->sizeCharacters = sizeCharacters;
338
339 const bool init = TTF_Init();
340 assert(init);
341 (void) init;
342
343 _cache = $$(Dictionary, dictionary);
344 assert(_cache);
345
346 _fonts = $$(Array, array);
347 assert(_fonts);
348}
349
353static void destroy(Class *clazz) {
354
355 release(_cache);
356 release(_fonts);
357
358 TTF_Quit();
359}
360
365Class *_Font(void) {
366 static Class *clazz;
367 static Once once;
368
369 do_once(&once, {
370 clazz = _initialize(&(const ClassDef) {
371 .name = "Font",
372 .superclass = _Object(),
373 .instanceSize = sizeof(Font),
374 .interfaceOffset = offsetof(Font, interface),
375 .interfaceSize = sizeof(FontInterface),
377 .destroy = destroy,
378 });
379 });
380
381 return clazz;
382}
383
384#undef _Class
static View * init(View *self)
Definition Box.c:67
static void destroy(Class *clazz)
Definition Font.c:353
static void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition Font.c:274
static SDL_Surface * renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth)
Definition Font.c:224
static void clearCache(void)
Definition Font.c:170
static Font * cachedFont(const char *family, int size, int style)
Definition Font.c:127
static bool isEqual(const Object *self, const Object *other)
Definition Font.c:88
Class * _Font(void)
Definition Font.c:365
static void renderDeviceDidReset(Font *self)
Definition Font.c:248
static Font * initWithData(Font *self, Data *data, const char *family, int size, int style)
Definition Font.c:197
static void dealloc(Object *self)
Definition Font.c:56
static Dictionary * _cache
Definition Font.c:112
const EnumName FontStyleNames[]
Definition Font.c:41
static void initialize(Class *clazz)
Definition Font.c:324
static void cacheFont(Data *data, const char *family)
Definition Font.c:119
static Array * _fonts
Definition Font.c:113
static int hash(const Object *self)
Definition Font.c:72
static Font * defaultFont(void)
Definition Font.c:178
TrueType fonts.
#define DEFAULT_FONT_STYLE
Definition Font.h:36
@ FontStyleUnderline
Definition Font.h:50
@ FontStyleBold
Definition Font.h:48
@ FontStyleStrikeThrough
Definition Font.h:51
@ FontStyleRegular
Definition Font.h:47
@ FontStyleItalic
Definition Font.h:49
#define DEFAULT_FONT_FAMILY
Definition Font.h:34
#define DEFAULT_FONT_SIZE
Definition Font.h:35
static SDL_Size size(const Image *self)
Definition Image.c:181
View logging facilities via SDL_Log.
#define MVC_LogWarn(fmt,...)
Definition Log.h:54
#define MVC_LogError(fmt,...)
Definition Log.h:57
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
TrueType fonts.
Definition Font.h:63
void(* cacheFont)(Data *data, const char *family)
Caches the specified font Data.
Definition Font.h:142
float scale
The display pixel density scale (e.g. 2.0 on Retina). Updated by callers via the scale field before i...
Definition Font.h:100
int renderSize
The render size, adjusted for display density.
Definition Font.h:94
char * family
The family name.
Definition Font.h:84
void renderCharacters(const Font *self, const char *chars, SDL_Color color, int wrapWidth)
Renders the given characters in this Font.
Definition Font.c:224
Font * initWithData(Font *self, Data *data, int size, int index)
int size
The point size.
Definition Font.h:105
TTF_Font * font
The backing font.
Definition Font.h:89
Data * data
The raw font data.
Definition Font.h:79
int style
The style.
Definition Font.h:110