1 /*
2 Copyright (c) 2014-2017, 2019, 2021 Cong Xu
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
10 Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 POSSIBILITY OF SUCH DAMAGE.
25 */
26 #include "font.h"
27
28 #include <stdio.h>
29 #include <string.h>
30
31 #ifdef __EMSCRIPTEN__
32 #include <SDL2/SDL_image.h>
33 #else
34 #include <SDL_image.h>
35 #endif
36
37 #include "blit.h"
38 #include "pic.h"
39 #include "sys_config.h"
40 #include "utils.h"
41
42
43 #define FIRST_CHAR 0
44 #define LAST_CHAR 255
45
46 Font gFont;
47
48
FontOptsNew(void)49 FontOpts FontOptsNew(void)
50 {
51 FontOpts opts;
52 memset(&opts, 0, sizeof opts);
53 opts.Mask = colorWhite;
54 return opts;
55 }
56
FontLoad(Font * f,const char * imgPath,const bool isProportional,const struct vec2i spaceSize)57 void FontLoad(
58 Font *f, const char *imgPath, const bool isProportional,
59 const struct vec2i spaceSize)
60 {
61 char buf[CDOGS_PATH_MAX];
62 GetDataFilePath(buf, imgPath);
63 SDL_RWops *rwops = SDL_RWFromFile(buf, "rb");
64 CASSERT(IMG_isPNG(rwops), "Error: font file is not PNG");
65 SDL_Surface *image = IMG_Load_RW(rwops, 0);
66 rwops->close(rwops);
67 if (!image)
68 {
69 fprintf(stderr, "Cannot load font image: %s\n", IMG_GetError());
70 goto bail;
71 }
72 if (image->format->BytesPerPixel != 4)
73 {
74 perror("Cannot load non-32-bit image");
75 fprintf(stderr, "Only 32-bit depth images supported\n");
76 goto bail;
77 }
78
79 CArrayInit(&f->Chars, sizeof(Pic));
80
81 // Check that the image is big enough for the dimensions
82 const struct vec2i step = svec2i(
83 f->Size.x + f->Padding.Left + f->Padding.Right,
84 f->Size.y + f->Padding.Top + f->Padding.Bottom);
85 if (step.x * f->Stride > image->w || step.y > image->h)
86 {
87 fprintf(stderr,
88 "Error: font image not big enough for font data "
89 "Image %dx%d Size %dx%d Stride %d Padding %d,%d,%d,%d\n",
90 image->w, image->h,
91 f->Size.x, f->Size.y, f->Stride,
92 f->Padding.Left, f->Padding.Top,
93 f->Padding.Right, f->Padding.Bottom);
94 goto bail;
95 }
96
97 // Load letters from image file
98 SDL_LockSurface(image);
99 int chars = 0;
100 for (struct vec2i pos = svec2i_zero();
101 pos.y + step.y <= image->h && chars < LAST_CHAR - FIRST_CHAR + 1;
102 pos.y += step.y)
103 {
104 int x = 0;
105 for (pos.x = 0;
106 x < f->Stride && chars < LAST_CHAR - FIRST_CHAR + 1;
107 pos.x += step.x, x++, chars++)
108 {
109 Pic p;
110 PicLoad(
111 &p, f->Size,
112 svec2i_add(pos, svec2i(f->Padding.Left, f->Padding.Top)),
113 image);
114 if (chars == ' ')
115 {
116 PicShrink(&p, spaceSize, svec2i_zero());
117 }
118 else if (isProportional)
119 {
120 PicTrim(&p, true, false);
121 }
122 CArrayPushBack(&f->Chars, &p);
123 }
124 }
125 SDL_UnlockSurface(image);
126
127 bail:
128 SDL_FreeSurface(image);
129 }
FontTerminate(Font * f)130 void FontTerminate(Font *f)
131 {
132 CA_FOREACH(Pic, p, f->Chars)
133 PicFree(p);
134 CA_FOREACH_END()
135 CArrayTerminate(&f->Chars);
136 }
137
FontW(const char c)138 int FontW(const char c)
139 {
140 const Pic *p = CArrayGet(&gFont.Chars, (int)c - FIRST_CHAR);
141 return p->size.x + gFont.Gap.x;
142 }
FontH(void)143 int FontH(void)
144 {
145 return gFont.Size.y + gFont.Gap.y;
146 }
FontChSize(const char c)147 struct vec2i FontChSize(const char c)
148 {
149 return svec2i(FontW(c), FontH());
150 }
FontStrW(const char * s)151 int FontStrW(const char *s)
152 {
153 return FontSubstrW(s, (int)strlen(s));
154 }
FontSubstrW(const char * s,int len)155 int FontSubstrW(const char *s, int len)
156 {
157 if (len > (int)strlen(s))
158 {
159 len = (int)strlen(s);
160 }
161 // Find the width of the longest line, if this is a multi-line
162 int maxWidth = 0;
163 int w = 0;
164 for (int i = 0; i < len; i++, s++)
165 {
166 if (*s == '\n')
167 {
168 maxWidth = MAX(maxWidth, w);
169 w = 0;
170 }
171 else
172 {
173 w += FontW(*s);
174 }
175 }
176 maxWidth = MAX(maxWidth, w);
177 return maxWidth;
178 }
FontStrH(const char * s)179 int FontStrH(const char *s)
180 {
181 return FontStrNumLines(s) * FontH();
182 }
FontStrSize(const char * s)183 struct vec2i FontStrSize(const char *s)
184 {
185 struct vec2i size = svec2i_zero();
186 while (*s)
187 {
188 char *lineEnd = strchr(s, '\n');
189 size.y += FontH();
190 if (lineEnd)
191 {
192 size.x = MAX(size.x, FontSubstrW(s, (int)(lineEnd - s)));
193 s = lineEnd + 1;
194 }
195 else
196 {
197 size.x = MAX(size.x, FontStrW(s));
198 s += strlen(s);
199 }
200 }
201 return size;
202 }
FontStrNumLines(const char * s)203 int FontStrNumLines(const char *s)
204 {
205 int lines;
206 for (lines = 0; s != NULL; lines++)
207 {
208 s = strchr(s, '\n');
209 if (s)
210 {
211 s++;
212 }
213 }
214 return lines;
215 }
216
FontCh(const char c,const struct vec2i pos)217 struct vec2i FontCh(const char c, const struct vec2i pos)
218 {
219 return FontChMask(c, pos, colorWhite);
220 }
FontChMask(const char c,const struct vec2i pos,const color_t mask)221 struct vec2i FontChMask(const char c, const struct vec2i pos, const color_t mask)
222 {
223 int idx = (int)c - FIRST_CHAR;
224 if (idx < 0)
225 {
226 idx += 256;
227 }
228 if (idx < FIRST_CHAR || idx > LAST_CHAR)
229 {
230 fprintf(stderr, "invalid char %d\n", idx);
231 idx = FIRST_CHAR;
232 }
233 const Pic *pic = CArrayGet(&gFont.Chars, idx);
234 PicRender(
235 pic, gGraphicsDevice.gameWindow.renderer, pos, mask, 0, svec2_one(),
236 SDL_FLIP_NONE, Rect2iZero());
237 // Add gap between characters
238 return svec2i(pos.x + pic->size.x + gFont.Gap.x, pos.y);
239 }
FontStr(const char * s,struct vec2i pos)240 struct vec2i FontStr(const char *s, struct vec2i pos)
241 {
242 return FontStrMask(s, pos, colorWhite);
243 }
FontStrMask(const char * s,struct vec2i pos,const color_t mask)244 struct vec2i FontStrMask(const char *s, struct vec2i pos, const color_t mask)
245 {
246 if (s == NULL)
247 {
248 return pos;
249 }
250 int left = pos.x;
251 while (*s)
252 {
253 if (*s == '\n')
254 {
255 pos.x = left;
256 pos.y += FontH();
257 }
258 else
259 {
260 pos = FontChMask(*s, pos, mask);
261 }
262 s++;
263 }
264 return pos;
265 }
FontStrMaskWrap(const char * s,struct vec2i pos,color_t mask,const int width)266 struct vec2i FontStrMaskWrap(const char *s, struct vec2i pos, color_t mask, const int width)
267 {
268 char buf[1024];
269 CASSERT(strlen(s) < 1024, "string too long to wrap");
270 FontSplitLines(s, buf, width);
271 return FontStrMask(buf, pos, mask);
272 }
273 static struct vec2i GetStrPos(const char *s, struct vec2i pos, const FontOpts opts);
FontStrOpt(const char * s,struct vec2i pos,const FontOpts opts)274 void FontStrOpt(const char *s, struct vec2i pos, const FontOpts opts)
275 {
276 if (s == NULL)
277 {
278 return;
279 }
280 pos = GetStrPos(s, pos, opts);
281 FontStrMask(s, pos, opts.Mask);
282 }
283 static int GetAlign(
284 const FontAlign align,
285 const int pos, const int pad, const int area, const int size);
GetStrPos(const char * s,struct vec2i pos,const FontOpts opts)286 static struct vec2i GetStrPos(const char *s, struct vec2i pos, const FontOpts opts)
287 {
288 const struct vec2i textSize = FontStrSize(s);
289 return svec2i(
290 GetAlign(opts.HAlign, pos.x, opts.Pad.x, opts.Area.x, textSize.x),
291 GetAlign(opts.VAlign, pos.y, opts.Pad.y, opts.Area.y, textSize.y));
292 }
GetAlign(const FontAlign align,const int pos,const int pad,const int area,const int size)293 static int GetAlign(
294 const FontAlign align,
295 const int pos, const int pad, const int area, const int size)
296 {
297 switch (align)
298 {
299 case ALIGN_START:
300 return pos + pad;
301 break;
302 case ALIGN_CENTER:
303 return pos + (area - size) / 2;
304 break;
305 case ALIGN_END:
306 return pos + area - size - pad;
307 break;
308 default:
309 CASSERT(false, "unknown align");
310 return pos;
311 }
312 }
313
FontStrCenter(const char * s)314 void FontStrCenter(const char *s)
315 {
316 FontOpts opts = FontOptsNew();
317 opts.HAlign = ALIGN_CENTER;
318 opts.VAlign = ALIGN_CENTER;
319 opts.Area = gGraphicsDevice.cachedConfig.Res;
320 FontStrOpt(s, svec2i_zero(), opts);
321 }
322
FontSplitLines(const char * text,char * buf,const int width)323 void FontSplitLines(const char *text, char *buf, const int width)
324 {
325 if (width == 0)
326 {
327 strcpy(buf, text);
328 return;
329 }
330 int ix, x;
331 const char *ws, *word, *s;
332
333 ix = x = CenterX(width);
334 s = ws = word = text;
335
336 while (*s)
337 {
338 // Skip spaces
339 ws = s;
340 while (*s == ' ' || *s == '\n')
341 {
342 *buf++ = *s;
343 if (*s == '\n')
344 {
345 // We've already skipped a word, so reset the word count
346 ws = s + 1;
347 x = ix;
348 }
349 s++;
350 }
351
352 // Find word
353 word = s;
354 while (*s != 0 && *s != ' ' && *s != '\n')
355 {
356 s++;
357 }
358 // Calculate width of word
359 int w;
360 const char *p;
361 for (w = 0, p = ws; p < s; p++)
362 {
363 w += FontW(*p);
364 }
365
366 // Create new line if text too wide
367 if (x + w > width + ix && w < width)
368 {
369 x = ix;
370 ws = word;
371 *buf++ = '\n';
372 }
373
374 for (p = ws; p < word; p++)
375 {
376 x += FontW(*p);
377 }
378
379 for (p = word; p < s; p++)
380 {
381 *buf++ = *p;
382 x += FontW(*p);
383 }
384 }
385 *buf = '\0';
386 }
387
Vec2iAligned(const struct vec2i v,const struct vec2i size,const FontAlign hAlign,const FontAlign vAlign,const struct vec2i area)388 struct vec2i Vec2iAligned(
389 const struct vec2i v, const struct vec2i size,
390 const FontAlign hAlign, const FontAlign vAlign, const struct vec2i area)
391 {
392 struct vec2i vAligned = v;
393 switch (hAlign)
394 {
395 case ALIGN_CENTER:
396 vAligned.x += (area.x - size.x) / 2;
397 break;
398 case ALIGN_END:
399 vAligned.x = -v.x + area.x - size.x;
400 break;
401 default:
402 break;
403 }
404 switch (vAlign)
405 {
406 case ALIGN_CENTER:
407 vAligned.y += (area.y - size.y) / 2;
408 break;
409 case ALIGN_END:
410 vAligned.y = -v.y + area.y - size.y;
411 break;
412 default:
413 break;
414 }
415 return vAligned;
416 }
417