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