1/**********************************************************************************************
2*
3* raylib.text - Basic functions to load Fonts and draw Text
4*
5* CONFIGURATION:
6*
7* #define SUPPORT_FILEFORMAT_FNT
8* #define SUPPORT_FILEFORMAT_TTF
9* Selected desired fileformats to be supported for loading. Some of those formats are
10* supported by default, to remove support, just comment unrequired #define in this module
11*
12* #define SUPPORT_DEFAULT_FONT
13* Load default raylib font on initialization to be used by DrawText() and MeasureText().
14* If no default font loaded, DrawTextEx() and MeasureTextEx() are required.
15*
16* #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH
17* TextSplit() function static buffer max size
18*
19* #define TEXTSPLIT_MAX_SUBSTRINGS_COUNT
20* TextSplit() function static substrings pointers array (pointing to static buffer)
21*
22*
23* DEPENDENCIES:
24* stb_truetype - Load TTF file and rasterize characters data
25* stb_rect_pack - Rectangles packing algorythms, required for font atlas generation
26*
27*
28* LICENSE: zlib/libpng
29*
30* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5)
31*
32* This software is provided "as-is", without any express or implied warranty. In no event
33* will the authors be held liable for any damages arising from the use of this software.
34*
35* Permission is granted to anyone to use this software for any purpose, including commercial
36* applications, and to alter it and redistribute it freely, subject to the following restrictions:
37*
38* 1. The origin of this software must not be misrepresented; you must not claim that you
39* wrote the original software. If you use this software in a product, an acknowledgment
40* in the product documentation would be appreciated but is not required.
41*
42* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
43* as being the original software.
44*
45* 3. This notice may not be removed or altered from any source distribution.
46*
47**********************************************************************************************/
48
49#include "raylib.h" // Declares module functions
50
51// Check if config flags have been externally provided on compilation line
52#if !defined(EXTERNAL_CONFIG_FLAGS)
53 #include "config.h" // Defines module configuration flags
54#endif
55
56#include <stdlib.h> // Required for: malloc(), free()
57#include <stdio.h> // Required for: FILE, fopen(), fclose(), fgets()
58#include <string.h> // Required for: strcmp(), strstr(), strcpy(), strncpy(), strcat(), strncat(), sscanf()
59#include <stdarg.h> // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()]
60#include <ctype.h> // Requried for: toupper(), tolower() [Used in TextToUpper(), TextToLower()]
61
62#include "utils.h" // Required for: fopen() Android mapping
63
64#if defined(SUPPORT_FILEFORMAT_TTF)
65 #define STB_RECT_PACK_IMPLEMENTATION
66 #include "external/stb_rect_pack.h" // Required for: ttf font rectangles packaging
67
68 #define STBTT_STATIC
69 #define STB_TRUETYPE_IMPLEMENTATION
70 #include "external/stb_truetype.h" // Required for: ttf font data reading
71#endif
72
73//----------------------------------------------------------------------------------
74// Defines and Macros
75//----------------------------------------------------------------------------------
76#define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions:
77 // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal()
78
79#define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints
80
81#if !defined(TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH)
82 #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH 1024 // Size of static buffer: TextSplit()
83#endif
84
85#if !defined(TEXTSPLIT_MAX_SUBSTRINGS_COUNT)
86 #define TEXTSPLIT_MAX_SUBSTRINGS_COUNT 128 // Size of static pointers array: TextSplit()
87#endif
88
89//----------------------------------------------------------------------------------
90// Types and Structures Definition
91//----------------------------------------------------------------------------------
92// ...
93
94//----------------------------------------------------------------------------------
95// Global variables
96//----------------------------------------------------------------------------------
97#if defined(SUPPORT_DEFAULT_FONT)
98static Font defaultFont = { 0 }; // Default font provided by raylib
99// NOTE: defaultFont is loaded on InitWindow and disposed on CloseWindow [module: core]
100#endif
101
102//----------------------------------------------------------------------------------
103// Other Modules Functions Declaration (required by text)
104//----------------------------------------------------------------------------------
105//...
106
107//----------------------------------------------------------------------------------
108// Module specific Functions Declaration
109//----------------------------------------------------------------------------------
110#if defined(SUPPORT_FILEFORMAT_FNT)
111static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file)
112#endif
113
114#if defined(SUPPORT_DEFAULT_FONT)
115extern void LoadFontDefault(void);
116extern void UnloadFontDefault(void);
117#endif
118
119//----------------------------------------------------------------------------------
120// Module Functions Definition
121//----------------------------------------------------------------------------------
122#if defined(SUPPORT_DEFAULT_FONT)
123
124// Load raylib default font
125extern void LoadFontDefault(void)
126{
127 #define BIT_CHECK(a,b) ((a) & (1u << (b)))
128
129 // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement
130 // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl
131
132 defaultFont.charsCount = 224; // Number of chars included in our default font
133
134 // Default font is directly defined here (data generated from a sprite font image)
135 // This way, we reconstruct Font without creating large global variables
136 // This data is automatically allocated to Stack and automatically deallocated at the end of this function
137 unsigned int defaultFontData[512] = {
138 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f,
139 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de,
140 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f,
141 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
142 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048,
143 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048,
144 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000,
145 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180,
146 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090,
147 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082,
148 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
149 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800,
150 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820,
151 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0,
152 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
153 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000,
154 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000,
155 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000,
156 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03,
157 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202,
158 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002,
159 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002,
160 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002,
161 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010,
162 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7,
163 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
164 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a,
165 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b,
166 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008,
167 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210,
168 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe,
169 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2,
170 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000,
171 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000,
172 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000,
173 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000,
174 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
175 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
176 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
177 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
178 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
179 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
180 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 };
181
182 int charsHeight = 10;
183 int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically
184
185 int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6,
186 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5,
187 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4,
188 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
189 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6,
190 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6,
191 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 };
192
193 // Re-construct image from defaultFontData and generate OpenGL texture
194 //----------------------------------------------------------------------
195 int imWidth = 128;
196 int imHeight = 128;
197
198 Color *imagePixels = (Color *)RL_MALLOC(imWidth*imHeight*sizeof(Color));
199
200 for (int i = 0; i < imWidth*imHeight; i++) imagePixels[i] = BLANK; // Initialize array
201
202 int counter = 0; // Font data elements counter
203
204 // Fill imgData with defaultFontData (convert from bit to pixel!)
205 for (int i = 0; i < imWidth*imHeight; i += 32)
206 {
207 for (int j = 31; j >= 0; j--)
208 {
209 if (BIT_CHECK(defaultFontData[counter], j)) imagePixels[i+j] = WHITE;
210 }
211
212 counter++;
213
214 if (counter > 512) counter = 0; // Security check...
215 }
216
217 Image imFont = LoadImageEx(imagePixels, imWidth, imHeight);
218 ImageFormat(&imFont, UNCOMPRESSED_GRAY_ALPHA);
219
220 RL_FREE(imagePixels);
221
222 defaultFont.texture = LoadTextureFromImage(imFont);
223
224 // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount
225 //------------------------------------------------------------------------------
226
227 // Allocate space for our characters info data
228 // NOTE: This memory should be freed at end! --> CloseWindow()
229 defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo));
230 defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle));
231
232 int currentLine = 0;
233 int currentPosX = charsDivisor;
234 int testPosX = charsDivisor;
235
236 for (int i = 0; i < defaultFont.charsCount; i++)
237 {
238 defaultFont.chars[i].value = 32 + i; // First char is 32
239
240 defaultFont.recs[i].x = (float)currentPosX;
241 defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
242 defaultFont.recs[i].width = (float)charsWidth[i];
243 defaultFont.recs[i].height = (float)charsHeight;
244
245 testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor);
246
247 if (testPosX >= defaultFont.texture.width)
248 {
249 currentLine++;
250 currentPosX = 2*charsDivisor + charsWidth[i];
251 testPosX = currentPosX;
252
253 defaultFont.recs[i].x = (float)charsDivisor;
254 defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
255 }
256 else currentPosX = testPosX;
257
258 // NOTE: On default font character offsets and xAdvance are not required
259 defaultFont.chars[i].offsetX = 0;
260 defaultFont.chars[i].offsetY = 0;
261 defaultFont.chars[i].advanceX = 0;
262
263 // Fill character image data from fontClear data
264 defaultFont.chars[i].image = ImageFromImage(imFont, defaultFont.recs[i]);
265 }
266
267 UnloadImage(imFont);
268
269 defaultFont.baseSize = (int)defaultFont.recs[0].height;
270
271 TRACELOG(LOG_INFO, "FONT: Default font loaded successfully");
272}
273
274// Unload raylib default font
275extern void UnloadFontDefault(void)
276{
277 for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image);
278 UnloadTexture(defaultFont.texture);
279 RL_FREE(defaultFont.chars);
280 RL_FREE(defaultFont.recs);
281}
282#endif // SUPPORT_DEFAULT_FONT
283
284// Get the default font, useful to be used with extended parameters
285Font GetFontDefault()
286{
287#if defined(SUPPORT_DEFAULT_FONT)
288 return defaultFont;
289#else
290 Font font = { 0 };
291 return font;
292#endif
293}
294
295// Load Font from file into GPU memory (VRAM)
296Font LoadFont(const char *fileName)
297{
298 // Default hardcoded values for ttf file loading
299 #define DEFAULT_TTF_FONTSIZE 32 // Font first character (32 - space)
300 #define DEFAULT_TTF_NUMCHARS 95 // ASCII 32..126 is 95 glyphs
301 #define DEFAULT_FIRST_CHAR 32 // Expected first char for image sprite font
302
303 Font font = { 0 };
304
305#if defined(SUPPORT_FILEFORMAT_TTF)
306 if (IsFileExtension(fileName, ".ttf;.otf")) font = LoadFontEx(fileName, DEFAULT_TTF_FONTSIZE, NULL, DEFAULT_TTF_NUMCHARS);
307 else
308#endif
309#if defined(SUPPORT_FILEFORMAT_FNT)
310 if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName);
311 else
312#endif
313 {
314 Image image = LoadImage(fileName);
315 if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, DEFAULT_FIRST_CHAR);
316 UnloadImage(image);
317 }
318
319 if (font.texture.id == 0)
320 {
321 TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName);
322 font = GetFontDefault();
323 }
324 else SetTextureFilter(font.texture, FILTER_POINT); // By default we set point filter (best performance)
325
326 return font;
327}
328
329// Load Font from TTF font file with generation parameters
330// NOTE: You can pass an array with desired characters, those characters should be available in the font
331// if array is NULL, default char set is selected 32..126
332Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount)
333{
334 Font font = { 0 };
335
336#if defined(SUPPORT_FILEFORMAT_TTF)
337 font.baseSize = fontSize;
338 font.charsCount = (charsCount > 0)? charsCount : 95;
339 font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT);
340
341 if (font.chars != NULL)
342 {
343 Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, 2, 0);
344 font.texture = LoadTextureFromImage(atlas);
345
346 // Update chars[i].image to use alpha, required to be used on ImageDrawText()
347 for (int i = 0; i < font.charsCount; i++)
348 {
349 UnloadImage(font.chars[i].image);
350 font.chars[i].image = ImageFromImage(atlas, font.recs[i]);
351 }
352
353 UnloadImage(atlas);
354 }
355 else font = GetFontDefault();
356#else
357 font = GetFontDefault();
358#endif
359
360 return font;
361}
362
363// Load an Image font file (XNA style)
364Font LoadFontFromImage(Image image, Color key, int firstChar)
365{
366 #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a))
367
368 int charSpacing = 0;
369 int lineSpacing = 0;
370
371 int x = 0;
372 int y = 0;
373
374 // Default number of characters supported
375 #define MAX_FONTCHARS 256
376
377 // We allocate a temporal arrays for chars data measures,
378 // once we get the actual number of chars, we copy data to a sized arrays
379 int tempCharValues[MAX_FONTCHARS];
380 Rectangle tempCharRecs[MAX_FONTCHARS];
381
382 Color *pixels = GetImageData(image);
383
384 // Parse image data to get charSpacing and lineSpacing
385 for (y = 0; y < image.height; y++)
386 {
387 for (x = 0; x < image.width; x++)
388 {
389 if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
390 }
391
392 if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
393 }
394
395 charSpacing = x;
396 lineSpacing = y;
397
398 int charHeight = 0;
399 int j = 0;
400
401 while (!COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++;
402
403 charHeight = j;
404
405 // Check array values to get characters: value, x, y, w, h
406 int index = 0;
407 int lineToRead = 0;
408 int xPosToRead = charSpacing;
409
410 // Parse image data to get rectangle sizes
411 while ((lineSpacing + lineToRead*(charHeight + lineSpacing)) < image.height)
412 {
413 while ((xPosToRead < image.width) &&
414 !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key))
415 {
416 tempCharValues[index] = firstChar + index;
417
418 tempCharRecs[index].x = (float)xPosToRead;
419 tempCharRecs[index].y = (float)(lineSpacing + lineToRead*(charHeight + lineSpacing));
420 tempCharRecs[index].height = (float)charHeight;
421
422 int charWidth = 0;
423
424 while (!COLOR_EQUAL(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++;
425
426 tempCharRecs[index].width = (float)charWidth;
427
428 index++;
429
430 xPosToRead += (charWidth + charSpacing);
431 }
432
433 lineToRead++;
434 xPosToRead = charSpacing;
435 }
436
437 // NOTE: We need to remove key color borders from image to avoid weird
438 // artifacts on texture scaling when using FILTER_BILINEAR or FILTER_TRILINEAR
439 for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK;
440
441 // Create a new image with the processed color data (key color replaced by BLANK)
442 Image fontClear = LoadImageEx(pixels, image.width, image.height);
443
444 RL_FREE(pixels); // Free pixels array memory
445
446 // Create spritefont with all data parsed from image
447 Font font = { 0 };
448
449 font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture
450 font.charsCount = index;
451
452 // We got tempCharValues and tempCharsRecs populated with chars data
453 // Now we move temp data to sized charValues and charRecs arrays
454 font.chars = (CharInfo *)RL_MALLOC(font.charsCount*sizeof(CharInfo));
455 font.recs = (Rectangle *)RL_MALLOC(font.charsCount*sizeof(Rectangle));
456
457 for (int i = 0; i < font.charsCount; i++)
458 {
459 font.chars[i].value = tempCharValues[i];
460
461 // Get character rectangle in the font atlas texture
462 font.recs[i] = tempCharRecs[i];
463
464 // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0)
465 font.chars[i].offsetX = 0;
466 font.chars[i].offsetY = 0;
467 font.chars[i].advanceX = 0;
468
469 // Fill character image data from fontClear data
470 font.chars[i].image = ImageFromImage(fontClear, tempCharRecs[i]);
471 }
472
473 UnloadImage(fontClear); // Unload processed image once converted to texture
474
475 font.baseSize = (int)font.recs[0].height;
476
477 return font;
478}
479
480// Load font data for further use
481// NOTE: Requires TTF font and can generate SDF data
482CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type)
483{
484 // NOTE: Using some SDF generation default values,
485 // trades off precision with ability to handle *smaller* sizes
486 #define SDF_CHAR_PADDING 4
487 #define SDF_ON_EDGE_VALUE 128
488 #define SDF_PIXEL_DIST_SCALE 64.0f
489
490 #define BITMAP_ALPHA_THRESHOLD 80
491
492 CharInfo *chars = NULL;
493
494#if defined(SUPPORT_FILEFORMAT_TTF)
495 // Load font data (including pixel data) from TTF file
496 // NOTE: Loaded information should be enough to generate
497 // font image atlas, using any packaging method
498 unsigned int dataSize = 0;
499 unsigned char *fileData = LoadFileData(fileName, &dataSize);
500
501 if (fileData != NULL)
502 {
503 int genFontChars = false;
504 stbtt_fontinfo fontInfo = { 0 };
505
506 if (stbtt_InitFont(&fontInfo, fileData, 0)) // Init font for data reading
507 {
508 // Calculate font scale factor
509 float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize);
510
511 // Calculate font basic metrics
512 // NOTE: ascent is equivalent to font baseline
513 int ascent, descent, lineGap;
514 stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
515
516 // In case no chars count provided, default to 95
517 charsCount = (charsCount > 0)? charsCount : 95;
518
519 // Fill fontChars in case not provided externally
520 // NOTE: By default we fill charsCount consecutevely, starting at 32 (Space)
521
522 if (fontChars == NULL)
523 {
524 fontChars = (int *)RL_MALLOC(charsCount*sizeof(int));
525 for (int i = 0; i < charsCount; i++) fontChars[i] = i + 32;
526 genFontChars = true;
527 }
528
529 chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo));
530
531 // NOTE: Using simple packaging, one char after another
532 for (int i = 0; i < charsCount; i++)
533 {
534 int chw = 0, chh = 0; // Character width and height (on generation)
535 int ch = fontChars[i]; // Character value to get info for
536 chars[i].value = ch;
537
538 // Render a unicode codepoint to a bitmap
539 // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap
540 // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be
541 // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide
542
543 if (type != FONT_SDF) chars[i].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY);
544 else if (ch != 32) chars[i].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY);
545 else chars[i].image.data = NULL;
546
547 stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL);
548 chars[i].advanceX = (int)((float)chars[i].advanceX*scaleFactor);
549
550 // Load characters images
551 chars[i].image.width = chw;
552 chars[i].image.height = chh;
553 chars[i].image.mipmaps = 1;
554 chars[i].image.format = UNCOMPRESSED_GRAYSCALE;
555
556 chars[i].offsetY += (int)((float)ascent*scaleFactor);
557
558 // NOTE: We create an empty image for space character, it could be further required for atlas packing
559 if (ch == 32)
560 {
561 chars[i].image = GenImageColor(chars[i].advanceX, fontSize, BLANK);
562 ImageFormat(&chars[i].image, UNCOMPRESSED_GRAYSCALE);
563 }
564
565 if (type == FONT_BITMAP)
566 {
567 // Aliased bitmap (black & white) font generation, avoiding anti-aliasing
568 // NOTE: For optimum results, bitmap font should be generated at base pixel size
569 for (int p = 0; p < chw*chh; p++)
570 {
571 if (((unsigned char *)chars[i].image.data)[p] < BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0;
572 else ((unsigned char *)chars[i].image.data)[p] = 255;
573 }
574 }
575
576 // Get bounding box for character (may be offset to account for chars that dip above or below the line)
577 /*
578 int chX1, chY1, chX2, chY2;
579 stbtt_GetCodepointBitmapBox(&fontInfo, ch, scaleFactor, scaleFactor, &chX1, &chY1, &chX2, &chY2);
580
581 TRACELOGD("FONT: Character box measures: %i, %i, %i, %i", chX1, chY1, chX2 - chX1, chY2 - chY1);
582 TRACELOGD("FONT: Character offsetY: %i", (int)((float)ascent*scaleFactor) + chY1);
583 */
584 }
585 }
586 else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data");
587
588 RL_FREE(fileData);
589 if (genFontChars) RL_FREE(fontChars);
590 }
591#endif
592
593 return chars;
594}
595
596// Generate image font atlas using chars info
597// NOTE: Packing method: 0-Default, 1-Skyline
598#if defined(SUPPORT_FILEFORMAT_TTF)
599Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCount, int fontSize, int padding, int packMethod)
600{
601 Image atlas = { 0 };
602
603 *charRecs = NULL;
604
605 // In case no chars count provided we suppose default of 95
606 charsCount = (charsCount > 0)? charsCount : 95;
607
608 // NOTE: Rectangles memory is loaded here!
609 Rectangle *recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle));
610
611 // Calculate image size based on required pixel area
612 // NOTE 1: Image is forced to be squared and POT... very conservative!
613 // NOTE 2: SDF font characters already contain an internal padding,
614 // so image size would result bigger than default font type
615 float requiredArea = 0;
616 for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding));
617 float guessSize = sqrtf(requiredArea)*1.3f;
618 int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT
619
620 atlas.width = imageSize; // Atlas bitmap width
621 atlas.height = imageSize; // Atlas bitmap height
622 atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp)
623 atlas.format = UNCOMPRESSED_GRAYSCALE;
624 atlas.mipmaps = 1;
625
626 // DEBUG: We can see padding in the generated image setting a gray background...
627 //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100;
628
629 if (packMethod == 0) // Use basic packing algorythm
630 {
631 int offsetX = padding;
632 int offsetY = padding;
633
634 // NOTE: Using simple packaging, one char after another
635 for (int i = 0; i < charsCount; i++)
636 {
637 // Copy pixel data from fc.data to atlas
638 for (int y = 0; y < chars[i].image.height; y++)
639 {
640 for (int x = 0; x < chars[i].image.width; x++)
641 {
642 ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x];
643 }
644 }
645
646 // Fill chars rectangles in atlas info
647 recs[i].x = (float)offsetX;
648 recs[i].y = (float)offsetY;
649 recs[i].width = (float)chars[i].image.width;
650 recs[i].height = (float)chars[i].image.height;
651
652 // Move atlas position X for next character drawing
653 offsetX += (chars[i].image.width + 2*padding);
654
655 if (offsetX >= (atlas.width - chars[i].image.width - 2*padding))
656 {
657 offsetX = padding;
658
659 // NOTE: Be careful on offsetY for SDF fonts, by default SDF
660 // use an internal padding of 4 pixels, it means char rectangle
661 // height is bigger than fontSize, it could be up to (fontSize + 8)
662 offsetY += (fontSize + 2*padding);
663
664 if (offsetY > (atlas.height - fontSize - padding)) break;
665 }
666 }
667 }
668 else if (packMethod == 1) // Use Skyline rect packing algorythm (stb_pack_rect)
669 {
670 stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context));
671 stbrp_node *nodes = (stbrp_node *)RL_MALLOC(charsCount*sizeof(*nodes));
672
673 stbrp_init_target(context, atlas.width, atlas.height, nodes, charsCount);
674 stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(charsCount*sizeof(stbrp_rect));
675
676 // Fill rectangles for packaging
677 for (int i = 0; i < charsCount; i++)
678 {
679 rects[i].id = i;
680 rects[i].w = chars[i].image.width + 2*padding;
681 rects[i].h = chars[i].image.height + 2*padding;
682 }
683
684 // Package rectangles into atlas
685 stbrp_pack_rects(context, rects, charsCount);
686
687 for (int i = 0; i < charsCount; i++)
688 {
689 // It return char rectangles in atlas
690 recs[i].x = rects[i].x + (float)padding;
691 recs[i].y = rects[i].y + (float)padding;
692 recs[i].width = (float)chars[i].image.width;
693 recs[i].height = (float)chars[i].image.height;
694
695 if (rects[i].was_packed)
696 {
697 // Copy pixel data from fc.data to atlas
698 for (int y = 0; y < chars[i].image.height; y++)
699 {
700 for (int x = 0; x < chars[i].image.width; x++)
701 {
702 ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x];
703 }
704 }
705 }
706 else TRACELOG(LOG_WARNING, "FONT: Failed to package character (%i)", i);
707 }
708
709 RL_FREE(rects);
710 RL_FREE(nodes);
711 RL_FREE(context);
712 }
713
714 // TODO: Crop image if required for smaller size
715
716 // Convert image data from GRAYSCALE to GRAY_ALPHA
717 // WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation
718 unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels
719
720 for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2)
721 {
722 dataGrayAlpha[k] = 255;
723 dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i];
724 }
725
726 RL_FREE(atlas.data);
727 atlas.data = dataGrayAlpha;
728 atlas.format = UNCOMPRESSED_GRAY_ALPHA;
729
730 *charRecs = recs;
731
732 return atlas;
733}
734#endif
735
736// Unload Font from GPU memory (VRAM)
737void UnloadFont(Font font)
738{
739 // NOTE: Make sure font is not default font (fallback)
740 if (font.texture.id != GetFontDefault().texture.id)
741 {
742 for (int i = 0; i < font.charsCount; i++) UnloadImage(font.chars[i].image);
743
744 UnloadTexture(font.texture);
745 RL_FREE(font.chars);
746 RL_FREE(font.recs);
747
748 TRACELOGD("FONT: Unloaded font data from RAM and VRAM");
749 }
750}
751
752// Shows current FPS on top-left corner
753// NOTE: Uses default font
754void DrawFPS(int posX, int posY)
755{
756 DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, LIME);
757}
758
759// Draw text (using default font)
760// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used
761// NOTE: chars spacing is proportional to fontSize
762void DrawText(const char *text, int posX, int posY, int fontSize, Color color)
763{
764 // Check if default font has been loaded
765 if (GetFontDefault().texture.id != 0)
766 {
767 Vector2 position = { (float)posX, (float)posY };
768
769 int defaultFontSize = 10; // Default Font chars height in pixel
770 if (fontSize < defaultFontSize) fontSize = defaultFontSize;
771 int spacing = fontSize/defaultFontSize;
772
773 DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color);
774 }
775}
776
777// Draw one character (codepoint)
778void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint)
779{
780 // Character index position in sprite font
781 // NOTE: In case a codepoint is not available in the font, index returned points to '?'
782 int index = GetGlyphIndex(font, codepoint);
783
784 // Character rectangle on screen
785 // NOTE: Quad is scaled proportionally to base character width-height
786 Rectangle rec = { position.x, position.y, font.recs[index].width*scale, font.recs[index].height*scale };
787
788 DrawTexturePro(font.texture, font.recs[index], rec, (Vector2){ 0, 0 }, 0.0f, tint);
789}
790
791// Draw text using Font
792// NOTE: chars spacing is NOT proportional to fontSize
793void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint)
794{
795 int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop
796
797 int textOffsetY = 0; // Offset between lines (on line break '\n')
798 float textOffsetX = 0.0f; // Offset X to next character to draw
799
800 float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor
801
802 for (int i = 0; i < length; i++)
803 {
804 // Get next codepoint from byte string and glyph index in font
805 int codepointByteCount = 0;
806 int codepoint = GetNextCodepoint(&text[i], &codepointByteCount);
807 int index = GetGlyphIndex(font, codepoint);
808
809 // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
810 // but we need to draw all of the bad bytes using the '?' symbol moving one byte
811 if (codepoint == 0x3f) codepointByteCount = 1;
812
813 if (codepoint == '\n')
814 {
815 // NOTE: Fixed line spacing of 1.5 line-height
816 // TODO: Support custom line spacing defined by user
817 textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
818 textOffsetX = 0.0f;
819 }
820 else
821 {
822 if ((codepoint != ' ') && (codepoint != '\t'))
823 {
824 Rectangle rec = { position.x + textOffsetX + font.chars[index].offsetX*scaleFactor,
825 position.y + textOffsetY + font.chars[index].offsetY*scaleFactor,
826 font.recs[index].width*scaleFactor,
827 font.recs[index].height*scaleFactor };
828
829 DrawTexturePro(font.texture, font.recs[index], rec, (Vector2){ 0, 0 }, 0.0f, tint);
830 }
831
832 if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing);
833 else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + spacing);
834 }
835
836 i += (codepointByteCount - 1); // Move text bytes counter to next codepoint
837 }
838}
839
840// Draw text using font inside rectangle limits
841void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint)
842{
843 DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE);
844}
845
846// Draw text using font inside rectangle limits with support for text selection
847void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint)
848{
849 int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop
850
851 int textOffsetY = 0; // Offset between lines (on line break '\n')
852 float textOffsetX = 0.0f; // Offset X to next character to draw
853
854 float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor
855
856 // Word/character wrapping mechanism variables
857 enum { MEASURE_STATE = 0, DRAW_STATE = 1 };
858 int state = wordWrap? MEASURE_STATE : DRAW_STATE;
859
860 int startLine = -1; // Index where to begin drawing (where a line begins)
861 int endLine = -1; // Index where to stop drawing (where a line ends)
862 int lastk = -1; // Holds last value of the character position
863
864 for (int i = 0, k = 0; i < length; i++, k++)
865 {
866 // Get next codepoint from byte string and glyph index in font
867 int codepointByteCount = 0;
868 int codepoint = GetNextCodepoint(&text[i], &codepointByteCount);
869 int index = GetGlyphIndex(font, codepoint);
870
871 // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
872 // but we need to draw all of the bad bytes using the '?' symbol moving one byte
873 if (codepoint == 0x3f) codepointByteCount = 1;
874 i += (codepointByteCount - 1);
875
876 int glyphWidth = 0;
877 if (codepoint != '\n')
878 {
879 glyphWidth = (font.chars[index].advanceX == 0)?
880 (int)(font.recs[index].width*scaleFactor + spacing):
881 (int)(font.chars[index].advanceX*scaleFactor + spacing);
882 }
883
884 // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container
885 // We store this info in startLine and endLine, then we change states, draw the text between those two variables
886 // and change states again and again recursively until the end of the text (or until we get outside of the container).
887 // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately
888 // and begin drawing on the next line before we can get outside the container.
889 if (state == MEASURE_STATE)
890 {
891 // TODO: There are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more
892 // Ref: http://jkorpela.fi/chars/spaces.html
893 if ((codepoint == ' ') || (codepoint == '\t') || (codepoint == '\n')) endLine = i;
894
895 if ((textOffsetX + glyphWidth + 1) >= rec.width)
896 {
897 endLine = (endLine < 1)? i : endLine;
898 if (i == endLine) endLine -= codepointByteCount;
899 if ((startLine + codepointByteCount) == endLine) endLine = (i - codepointByteCount);
900
901 state = !state;
902 }
903 else if ((i + 1) == length)
904 {
905 endLine = i;
906
907 state = !state;
908 }
909 else if (codepoint == '\n') state = !state;
910
911 if (state == DRAW_STATE)
912 {
913 textOffsetX = 0;
914 i = startLine;
915 glyphWidth = 0;
916
917 // Save character position when we switch states
918 int tmp = lastk;
919 lastk = k - 1;
920 k = tmp;
921 }
922 }
923 else
924 {
925 if (codepoint == '\n')
926 {
927 if (!wordWrap)
928 {
929 textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
930 textOffsetX = 0;
931 }
932 }
933 else
934 {
935 if (!wordWrap && ((textOffsetX + glyphWidth + 1) >= rec.width))
936 {
937 textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
938 textOffsetX = 0;
939 }
940
941 // When text overflows rectangle height limit, just stop drawing
942 if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break;
943
944 // Draw selection background
945 bool isGlyphSelected = false;
946 if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength)))
947 {
948 DrawRectangleRec((Rectangle){ rec.x + textOffsetX - 1, rec.y + textOffsetY, glyphWidth, (int)((float)font.baseSize*scaleFactor) }, selectBackTint);
949 isGlyphSelected = true;
950 }
951
952 // Draw current chracter glyph
953 if ((codepoint != ' ') && (codepoint != '\t'))
954 {
955 DrawTexturePro(font.texture, font.recs[index],
956 (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor,
957 rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor,
958 font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor },
959 (Vector2){ 0, 0 }, 0.0f, (!isGlyphSelected)? tint : selectTint);
960 }
961 }
962
963 if (wordWrap && (i == endLine))
964 {
965 textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
966 textOffsetX = 0;
967 startLine = endLine;
968 endLine = -1;
969 glyphWidth = 0;
970 selectStart += lastk - k;
971 k = lastk;
972
973 state = !state;
974 }
975 }
976
977 textOffsetX += glyphWidth;
978 }
979}
980
981// Measure string width for default font
982int MeasureText(const char *text, int fontSize)
983{
984 Vector2 vec = { 0.0f, 0.0f };
985
986 // Check if default font has been loaded
987 if (GetFontDefault().texture.id != 0)
988 {
989 int defaultFontSize = 10; // Default Font chars height in pixel
990 if (fontSize < defaultFontSize) fontSize = defaultFontSize;
991 int spacing = fontSize/defaultFontSize;
992
993 vec = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing);
994 }
995
996 return (int)vec.x;
997}
998
999// Measure string size for Font
1000Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing)
1001{
1002 int len = TextLength(text);
1003 int tempLen = 0; // Used to count longer text line num chars
1004 int lenCounter = 0;
1005
1006 float textWidth = 0.0f;
1007 float tempTextWidth = 0.0f; // Used to count longer text line width
1008
1009 float textHeight = (float)font.baseSize;
1010 float scaleFactor = fontSize/(float)font.baseSize;
1011
1012 int letter = 0; // Current character
1013 int index = 0; // Index position in sprite font
1014
1015 for (int i = 0; i < len; i++)
1016 {
1017 lenCounter++;
1018
1019 int next = 0;
1020 letter = GetNextCodepoint(&text[i], &next);
1021 index = GetGlyphIndex(font, letter);
1022
1023 // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
1024 // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
1025 if (letter == 0x3f) next = 1;
1026 i += next - 1;
1027
1028 if (letter != '\n')
1029 {
1030 if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX;
1031 else textWidth += (font.recs[index].width + font.chars[index].offsetX);
1032 }
1033 else
1034 {
1035 if (tempTextWidth < textWidth) tempTextWidth = textWidth;
1036 lenCounter = 0;
1037 textWidth = 0;
1038 textHeight += ((float)font.baseSize*1.5f); // NOTE: Fixed line spacing of 1.5 lines
1039 }
1040
1041 if (tempLen < lenCounter) tempLen = lenCounter;
1042 }
1043
1044 if (tempTextWidth < textWidth) tempTextWidth = textWidth;
1045
1046 Vector2 vec = { 0 };
1047 vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure
1048 vec.y = textHeight*scaleFactor;
1049
1050 return vec;
1051}
1052
1053// Returns index position for a unicode character on spritefont
1054int GetGlyphIndex(Font font, int codepoint)
1055{
1056#define TEXT_CHARACTER_NOTFOUND 63 // Character: '?'
1057
1058#define UNORDERED_CHARSET
1059#if defined(UNORDERED_CHARSET)
1060 int index = TEXT_CHARACTER_NOTFOUND;
1061
1062 for (int i = 0; i < font.charsCount; i++)
1063 {
1064 if (font.chars[i].value == codepoint)
1065 {
1066 index = i;
1067 break;
1068 }
1069 }
1070
1071 return index;
1072#else
1073 return (codepoint - 32);
1074#endif
1075}
1076
1077//----------------------------------------------------------------------------------
1078// Text strings management functions
1079//----------------------------------------------------------------------------------
1080
1081// Copy one string to another, returns bytes copied
1082int TextCopy(char *dst, const char *src)
1083{
1084 int bytes = 0;
1085
1086 if (dst != NULL)
1087 {
1088 while (*src != '\0')
1089 {
1090 *dst = *src;
1091 dst++;
1092 src++;
1093
1094 bytes++;
1095 }
1096
1097 *dst = '\0';
1098 }
1099
1100 return bytes;
1101}
1102
1103// Check if two text string are equal
1104// REQUIRES: strcmp()
1105bool TextIsEqual(const char *text1, const char *text2)
1106{
1107 bool result = false;
1108
1109 if (strcmp(text1, text2) == 0) result = true;
1110
1111 return result;
1112}
1113
1114// Get text length in bytes, check for \0 character
1115unsigned int TextLength(const char *text)
1116{
1117 unsigned int length = 0; //strlen(text)
1118
1119 if (text != NULL)
1120 {
1121 while (*text++) length++;
1122 }
1123
1124 return length;
1125}
1126
1127// Formatting of text with variables to 'embed'
1128// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times
1129const char *TextFormat(const char *text, ...)
1130{
1131 #define MAX_TEXTFORMAT_BUFFERS 4
1132
1133 // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations
1134 static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 };
1135 static int index = 0;
1136
1137 char *currentBuffer = buffers[index];
1138 memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using
1139
1140 va_list args;
1141 va_start(args, text);
1142 vsprintf(currentBuffer, text, args);
1143 va_end(args);
1144
1145 index += 1; // Move to next buffer for next function call
1146 if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0;
1147
1148 return currentBuffer;
1149}
1150
1151// Get a piece of a text string
1152const char *TextSubtext(const char *text, int position, int length)
1153{
1154 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1155
1156 int textLength = TextLength(text);
1157
1158 if (position >= textLength)
1159 {
1160 position = textLength - 1;
1161 length = 0;
1162 }
1163
1164 if (length >= textLength) length = textLength;
1165
1166 for (int c = 0 ; c < length ; c++)
1167 {
1168 *(buffer + c) = *(text + position);
1169 text++;
1170 }
1171
1172 *(buffer + length) = '\0';
1173
1174 return buffer;
1175}
1176
1177// Replace text string
1178// REQUIRES: strstr(), strncpy(), strcpy()
1179// WARNING: Internally allocated memory must be freed by the user (if return != NULL)
1180char *TextReplace(char *text, const char *replace, const char *by)
1181{
1182 // Sanity checks and initialization
1183 if (!text || !replace || !by) return NULL;
1184
1185 char *result;
1186
1187 char *insertPoint; // Next insert point
1188 char *temp; // Temp pointer
1189 int replaceLen; // Replace string length of (the string to remove)
1190 int byLen; // Replacement length (the string to replace replace by)
1191 int lastReplacePos; // Distance between replace and end of last replace
1192 int count; // Number of replacements
1193
1194 replaceLen = TextLength(replace);
1195 if (replaceLen == 0) return NULL; // Empty replace causes infinite loop during count
1196
1197 byLen = TextLength(by);
1198
1199 // Count the number of replacements needed
1200 insertPoint = text;
1201 for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen;
1202
1203 // Allocate returning string and point temp to it
1204 temp = result = RL_MALLOC(TextLength(text) + (byLen - replaceLen)*count + 1);
1205
1206 if (!result) return NULL; // Memory could not be allocated
1207
1208 // First time through the loop, all the variable are set correctly from here on,
1209 // temp points to the end of the result string
1210 // insertPoint points to the next occurrence of replace in text
1211 // text points to the remainder of text after "end of replace"
1212 while (count--)
1213 {
1214 insertPoint = strstr(text, replace);
1215 lastReplacePos = insertPoint - text;
1216 temp = strncpy(temp, text, lastReplacePos) + lastReplacePos;
1217 temp = strcpy(temp, by) + byLen;
1218 text += lastReplacePos + replaceLen; // Move to next "end of replace"
1219 }
1220
1221 // Copy remaind text part after replacement to result (pointed by moving temp)
1222 strcpy(temp, text);
1223
1224 return result;
1225}
1226
1227// Insert text in a specific position, moves all text forward
1228// WARNING: Allocated memory should be manually freed
1229char *TextInsert(const char *text, const char *insert, int position)
1230{
1231 int textLen = TextLength(text);
1232 int insertLen = TextLength(insert);
1233
1234 char *result = (char *)RL_MALLOC(textLen + insertLen + 1);
1235
1236 for (int i = 0; i < position; i++) result[i] = text[i];
1237 for (int i = position; i < insertLen + position; i++) result[i] = insert[i];
1238 for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i];
1239
1240 result[textLen + insertLen] = '\0'; // Make sure text string is valid!
1241
1242 return result;
1243}
1244
1245// Join text strings with delimiter
1246// REQUIRES: strcat()
1247const char *TextJoin(const char **textList, int count, const char *delimiter)
1248{
1249 static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1250 memset(text, 0, MAX_TEXT_BUFFER_LENGTH);
1251
1252 int totalLength = 0;
1253 int delimiterLen = TextLength(delimiter);
1254
1255 for (int i = 0; i < count; i++)
1256 {
1257 int textListLength = TextLength(textList[i]);
1258
1259 // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH
1260 if ((totalLength + textListLength) < MAX_TEXT_BUFFER_LENGTH)
1261 {
1262 strcat(text, textList[i]);
1263 totalLength += textListLength;
1264
1265 if ((delimiterLen > 0) && (i < (count - 1)))
1266 {
1267 strcat(text, delimiter);
1268 totalLength += delimiterLen;
1269 }
1270 }
1271 }
1272
1273 return text;
1274}
1275
1276// Split string into multiple strings
1277const char **TextSplit(const char *text, char delimiter, int *count)
1278{
1279 // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter)
1280 // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated,
1281 // all used memory is static... it has some limitations:
1282 // 1. Maximum number of possible split strings is set by TEXTSPLIT_MAX_SUBSTRINGS_COUNT
1283 // 2. Maximum size of text to split is TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH
1284
1285 static const char *result[TEXTSPLIT_MAX_SUBSTRINGS_COUNT] = { NULL };
1286 static char buffer[TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH] = { 0 };
1287 memset(buffer, 0, TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH);
1288
1289 result[0] = buffer;
1290 int counter = 0;
1291
1292 if (text != NULL)
1293 {
1294 counter = 1;
1295
1296 // Count how many substrings we have on text and point to every one
1297 for (int i = 0; i < TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH; i++)
1298 {
1299 buffer[i] = text[i];
1300 if (buffer[i] == '\0') break;
1301 else if (buffer[i] == delimiter)
1302 {
1303 buffer[i] = '\0'; // Set an end of string at this point
1304 result[counter] = buffer + i + 1;
1305 counter++;
1306
1307 if (counter == TEXTSPLIT_MAX_SUBSTRINGS_COUNT) break;
1308 }
1309 }
1310 }
1311
1312 *count = counter;
1313 return result;
1314}
1315
1316// Append text at specific position and move cursor!
1317// REQUIRES: strcpy()
1318void TextAppend(char *text, const char *append, int *position)
1319{
1320 strcpy(text + *position, append);
1321 *position += TextLength(append);
1322}
1323
1324// Find first text occurrence within a string
1325// REQUIRES: strstr()
1326int TextFindIndex(const char *text, const char *find)
1327{
1328 int position = -1;
1329
1330 char *ptr = strstr(text, find);
1331
1332 if (ptr != NULL) position = ptr - text;
1333
1334 return position;
1335}
1336
1337// Get upper case version of provided string
1338const char *TextToUpper(const char *text)
1339{
1340 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1341
1342 for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++)
1343 {
1344 if (text[i] != '\0')
1345 {
1346 buffer[i] = (char)toupper(text[i]);
1347 //if ((text[i] >= 'a') && (text[i] <= 'z')) buffer[i] = text[i] - 32;
1348
1349 // TODO: Support Utf8 diacritics!
1350 //if ((text[i] >= 'à') && (text[i] <= 'ý')) buffer[i] = text[i] - 32;
1351 }
1352 else { buffer[i] = '\0'; break; }
1353 }
1354
1355 return buffer;
1356}
1357
1358// Get lower case version of provided string
1359const char *TextToLower(const char *text)
1360{
1361 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1362
1363 for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++)
1364 {
1365 if (text[i] != '\0')
1366 {
1367 buffer[i] = (char)tolower(text[i]);
1368 //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32;
1369 }
1370 else { buffer[i] = '\0'; break; }
1371 }
1372
1373 return buffer;
1374}
1375
1376// Get Pascal case notation version of provided string
1377const char *TextToPascal(const char *text)
1378{
1379 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1380
1381 buffer[0] = (char)toupper(text[0]);
1382
1383 for (int i = 1, j = 1; i < MAX_TEXT_BUFFER_LENGTH; i++, j++)
1384 {
1385 if (text[j] != '\0')
1386 {
1387 if (text[j] != '_') buffer[i] = text[j];
1388 else
1389 {
1390 j++;
1391 buffer[i] = (char)toupper(text[j]);
1392 }
1393 }
1394 else { buffer[i] = '\0'; break; }
1395 }
1396
1397 return buffer;
1398}
1399
1400// Get integer value from text
1401// NOTE: This function replaces atoi() [stdlib.h]
1402int TextToInteger(const char *text)
1403{
1404 int value = 0;
1405 int sign = 1;
1406
1407 if ((text[0] == '+') || (text[0] == '-'))
1408 {
1409 if (text[0] == '-') sign = -1;
1410 text++;
1411 }
1412
1413 for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0');
1414
1415 return value*sign;
1416}
1417
1418// Encode text codepoint into utf8 text (memory must be freed!)
1419char *TextToUtf8(int *codepoints, int length)
1420{
1421 // We allocate enough memory fo fit all possible codepoints
1422 // NOTE: 5 bytes for every codepoint should be enough
1423 char *text = (char *)RL_CALLOC(length*5, 1);
1424 const char *utf8 = NULL;
1425 int size = 0;
1426
1427 for (int i = 0, bytes = 0; i < length; i++)
1428 {
1429 utf8 = CodepointToUtf8(codepoints[i], &bytes);
1430 strncpy(text + size, utf8, bytes);
1431 size += bytes;
1432 }
1433
1434 // Resize memory to text length + string NULL terminator
1435 void *ptr = RL_REALLOC(text, size + 1);
1436
1437 if (ptr != NULL) text = (char *)ptr;
1438
1439 return text;
1440}
1441
1442// Get all codepoints in a string, codepoints count returned by parameters
1443int *GetCodepoints(const char *text, int *count)
1444{
1445 static int codepoints[MAX_TEXT_UNICODE_CHARS] = { 0 };
1446 memset(codepoints, 0, MAX_TEXT_UNICODE_CHARS*sizeof(int));
1447
1448 int bytesProcessed = 0;
1449 int textLength = TextLength(text);
1450 int codepointsCount = 0;
1451
1452 for (int i = 0; i < textLength; codepointsCount++)
1453 {
1454 codepoints[codepointsCount] = GetNextCodepoint(text + i, &bytesProcessed);
1455 i += bytesProcessed;
1456 }
1457
1458 *count = codepointsCount;
1459
1460 return codepoints;
1461}
1462
1463// Returns total number of characters(codepoints) in a UTF8 encoded text, until '\0' is found
1464// NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead
1465int GetCodepointsCount(const char *text)
1466{
1467 unsigned int len = 0;
1468 char *ptr = (char *)&text[0];
1469
1470 while (*ptr != '\0')
1471 {
1472 int next = 0;
1473 int letter = GetNextCodepoint(ptr, &next);
1474
1475 if (letter == 0x3f) ptr += 1;
1476 else ptr += next;
1477
1478 len++;
1479 }
1480
1481 return len;
1482}
1483
1484
1485// Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found
1486// When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned
1487// Total number of bytes processed are returned as a parameter
1488// NOTE: the standard says U+FFFD should be returned in case of errors
1489// but that character is not supported by the default font in raylib
1490// TODO: optimize this code for speed!!
1491int GetNextCodepoint(const char *text, int *bytesProcessed)
1492{
1493/*
1494 UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt
1495
1496 Char. number range | UTF-8 octet sequence
1497 (hexadecimal) | (binary)
1498 --------------------+---------------------------------------------
1499 0000 0000-0000 007F | 0xxxxxxx
1500 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1501 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1502 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1503*/
1504 // NOTE: on decode errors we return as soon as possible
1505
1506 int code = 0x3f; // Codepoint (defaults to '?')
1507 int octet = (unsigned char)(text[0]); // The first UTF8 octet
1508 *bytesProcessed = 1;
1509
1510 if (octet <= 0x7f)
1511 {
1512 // Only one octet (ASCII range x00-7F)
1513 code = text[0];
1514 }
1515 else if ((octet & 0xe0) == 0xc0)
1516 {
1517 // Two octets
1518 // [0]xC2-DF [1]UTF8-tail(x80-BF)
1519 unsigned char octet1 = text[1];
1520
1521 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence
1522
1523 if ((octet >= 0xc2) && (octet <= 0xdf))
1524 {
1525 code = ((octet & 0x1f) << 6) | (octet1 & 0x3f);
1526 *bytesProcessed = 2;
1527 }
1528 }
1529 else if ((octet & 0xf0) == 0xe0)
1530 {
1531 // Three octets
1532 unsigned char octet1 = text[1];
1533 unsigned char octet2 = '\0';
1534
1535 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence
1536
1537 octet2 = text[2];
1538
1539 if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence
1540
1541 /*
1542 [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF)
1543 [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF)
1544 [0]xED [1]x80-9F [2]UTF8-tail(x80-BF)
1545 [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF)
1546 */
1547
1548 if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) ||
1549 ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; }
1550
1551 if ((octet >= 0xe0) && (0 <= 0xef))
1552 {
1553 code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);
1554 *bytesProcessed = 3;
1555 }
1556 }
1557 else if ((octet & 0xf8) == 0xf0)
1558 {
1559 // Four octets
1560 if (octet > 0xf4) return code;
1561
1562 unsigned char octet1 = text[1];
1563 unsigned char octet2 = '\0';
1564 unsigned char octet3 = '\0';
1565
1566 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence
1567
1568 octet2 = text[2];
1569
1570 if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence
1571
1572 octet3 = text[3];
1573
1574 if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence
1575
1576 /*
1577 [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail
1578 [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail
1579 [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail
1580 */
1581
1582 if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||
1583 ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence
1584
1585 if (octet >= 0xf0)
1586 {
1587 code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f);
1588 *bytesProcessed = 4;
1589 }
1590 }
1591
1592 if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid
1593
1594 return code;
1595}
1596
1597// Encode codepoint into utf8 text (char array length returned as parameter)
1598RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength)
1599{
1600 static char utf8[6] = { 0 };
1601 int length = 0;
1602
1603 if (codepoint <= 0x7f)
1604 {
1605 utf8[0] = (char)codepoint;
1606 length = 1;
1607 }
1608 else if (codepoint <= 0x7ff)
1609 {
1610 utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0);
1611 utf8[1] = (char)((codepoint & 0x3f) | 0x80);
1612 length = 2;
1613 }
1614 else if (codepoint <= 0xffff)
1615 {
1616 utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0);
1617 utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80);
1618 utf8[2] = (char)((codepoint & 0x3f) | 0x80);
1619 length = 3;
1620 }
1621 else if (codepoint <= 0x10ffff)
1622 {
1623 utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0);
1624 utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80);
1625 utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80);
1626 utf8[3] = (char)((codepoint & 0x3f) | 0x80);
1627 length = 4;
1628 }
1629
1630 *byteLength = length;
1631
1632 return utf8;
1633}
1634//----------------------------------------------------------------------------------
1635
1636//----------------------------------------------------------------------------------
1637// Module specific Functions Definition
1638//----------------------------------------------------------------------------------
1639#if defined(SUPPORT_FILEFORMAT_FNT)
1640// Load a BMFont file (AngelCode font file)
1641static Font LoadBMFont(const char *fileName)
1642{
1643 #define MAX_BUFFER_SIZE 256
1644
1645 Font font = { 0 };
1646
1647 char buffer[MAX_BUFFER_SIZE] = { 0 };
1648 char *searchPoint = NULL;
1649
1650 int fontSize = 0;
1651 int texWidth = 0;
1652 int texHeight = 0;
1653 char texFileName[129];
1654 int charsCount = 0;
1655
1656 int base = 0; // Useless data
1657
1658 FILE *fntFile = NULL;
1659
1660 fntFile = fopen(fileName, "rt");
1661
1662 if (fntFile == NULL)
1663 {
1664 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open FNT file", fileName);
1665 return font;
1666 }
1667
1668 // NOTE: We skip first line, it contains no useful information
1669 fgets(buffer, MAX_BUFFER_SIZE, fntFile);
1670 //searchPoint = strstr(buffer, "size");
1671 //sscanf(searchPoint, "size=%i", &fontSize);
1672
1673 fgets(buffer, MAX_BUFFER_SIZE, fntFile);
1674 searchPoint = strstr(buffer, "lineHeight");
1675 sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i", &fontSize, &base, &texWidth, &texHeight);
1676
1677 TRACELOGD("FONT: [%s] Loaded font info:", fileName);
1678 TRACELOGD(" > Base size: %i", fontSize);
1679 TRACELOGD(" > Texture scale: %ix%i", texWidth, texHeight);
1680
1681 fgets(buffer, MAX_BUFFER_SIZE, fntFile);
1682 searchPoint = strstr(buffer, "file");
1683 sscanf(searchPoint, "file=\"%128[^\"]\"", texFileName);
1684
1685 TRACELOGD(" > Texture filename: %s", texFileName);
1686
1687 fgets(buffer, MAX_BUFFER_SIZE, fntFile);
1688 searchPoint = strstr(buffer, "count");
1689 sscanf(searchPoint, "count=%i", &charsCount);
1690
1691 TRACELOGD(" > Chars count: %i", charsCount);
1692
1693 // Compose correct path using route of .fnt file (fileName) and texFileName
1694 char *texPath = NULL;
1695 char *lastSlash = NULL;
1696
1697 lastSlash = strrchr(fileName, '/');
1698 if (lastSlash == NULL)
1699 {
1700 lastSlash = strrchr(fileName, '\\');
1701 }
1702
1703 // NOTE: We need some extra space to avoid memory corruption on next allocations!
1704 texPath = RL_MALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(texFileName) + 4);
1705
1706 // NOTE: strcat() and strncat() required a '\0' terminated string to work!
1707 *texPath = '\0';
1708 strncat(texPath, fileName, TextLength(fileName) - TextLength(lastSlash) + 1);
1709 strncat(texPath, texFileName, TextLength(texFileName));
1710
1711 TRACELOGD(" > Texture loading path: %s", texPath);
1712
1713 Image imFont = LoadImage(texPath);
1714
1715 if (imFont.format == UNCOMPRESSED_GRAYSCALE)
1716 {
1717 // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel
1718 ImageAlphaMask(&imFont, imFont);
1719 for (int p = 0; p < (imFont.width*imFont.height*2); p += 2) ((unsigned char *)(imFont.data))[p] = 0xff;
1720 }
1721
1722 font.texture = LoadTextureFromImage(imFont);
1723
1724 RL_FREE(texPath);
1725
1726 // Fill font characters info data
1727 font.baseSize = fontSize;
1728 font.charsCount = charsCount;
1729 font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo));
1730 font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle));
1731
1732 int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX;
1733
1734 for (int i = 0; i < charsCount; i++)
1735 {
1736 fgets(buffer, MAX_BUFFER_SIZE, fntFile);
1737 sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i",
1738 &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX);
1739
1740 // Get character rectangle in the font atlas texture
1741 font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight };
1742
1743 // Save data properly in sprite font
1744 font.chars[i].value = charId;
1745 font.chars[i].offsetX = charOffsetX;
1746 font.chars[i].offsetY = charOffsetY;
1747 font.chars[i].advanceX = charAdvanceX;
1748
1749 // Fill character image data from imFont data
1750 font.chars[i].image = ImageFromImage(imFont, font.recs[i]);
1751 }
1752
1753 UnloadImage(imFont);
1754
1755 fclose(fntFile);
1756
1757 if (font.texture.id == 0)
1758 {
1759 UnloadFont(font);
1760 font = GetFontDefault();
1761 TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName);
1762 }
1763 else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully", fileName);
1764
1765 return font;
1766}
1767#endif
1768