0/**********************************************************************************************
1*
2* rtext - Basic functions to load fonts and draw text
3*
4* CONFIGURATION:
5* #define SUPPORT_MODULE_RTEXT
6* rtext module is included in the build
7*
8* #define SUPPORT_DEFAULT_FONT
9* Load default raylib font on initialization to be used by DrawText() and MeasureText()
10* If no default font loaded, DrawTextEx() and MeasureTextEx() are required
11*
12* #define SUPPORT_FILEFORMAT_FNT
13* #define SUPPORT_FILEFORMAT_TTF
14* #define SUPPORT_FILEFORMAT_BDF
15* Selected desired fileformats to be supported for loading. Some of those formats are
16* supported by default, to remove support, just comment unrequired #define in this module
17*
18* #define SUPPORT_FONT_ATLAS_WHITE_REC
19* On font atlas image generation [GenImageFontAtlas()], add a 3x3 pixels white rectangle
20* at the bottom-right corner of the atlas. It can be useful to for shapes drawing, to allow
21* drawing text and shapes with a single draw call [SetShapesTexture()]
22*
23* #define TEXTSPLIT_MAX_TEXT_BUFFER_LENGTH
24* TextSplit() function static buffer max size
25*
26* #define MAX_TEXTSPLIT_COUNT
27* TextSplit() function static substrings pointers array (pointing to static buffer)
28*
29* DEPENDENCIES:
30* stb_truetype - Load TTF file and rasterize characters data
31* stb_rect_pack - Rectangles packing algorithms, required for font atlas generation
32*
33*
34* LICENSE: zlib/libpng
35*
36* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)
37*
38* This software is provided "as-is", without any express or implied warranty. In no event
39* will the authors be held liable for any damages arising from the use of this software.
40*
41* Permission is granted to anyone to use this software for any purpose, including commercial
42* applications, and to alter it and redistribute it freely, subject to the following restrictions:
43*
44* 1. The origin of this software must not be misrepresented; you must not claim that you
45* wrote the original software. If you use this software in a product, an acknowledgment
46* in the product documentation would be appreciated but is not required.
47*
48* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
49* as being the original software.
50*
51* 3. This notice may not be removed or altered from any source distribution.
52*
53**********************************************************************************************/
55#include "raylib.h" // Declares module functions
57// Check if config flags have been externally provided on compilation line
58#if !defined(EXTERNAL_CONFIG_FLAGS)
59 #include "config.h" // Defines module configuration flags
60#endif
62#if defined(SUPPORT_MODULE_RTEXT)
64#include "utils.h" // Required for: LoadFile*()
65#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 2.1, 3.3+ or ES2 -> Only DrawTextPro()
67#include <stdlib.h> // Required for: malloc(), free()
68#include <stdio.h> // Required for: vsprintf()
69#include <string.h> // Required for: strcmp(), strstr(), strncpy() [Used in TextReplace()], sscanf() [Used in LoadBMFont()]
70#include <stdarg.h> // Required for: va_list, va_start(), vsprintf(), va_end() [Used in TextFormat()]
71#include <ctype.h> // Required for: toupper(), tolower() [Used in TextToUpper(), TextToLower()]
73#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
74 #if defined(__GNUC__) // GCC and Clang
75 #pragma GCC diagnostic push
76 #pragma GCC diagnostic ignored "-Wunused-function"
77 #endif
79 #define STB_RECT_PACK_IMPLEMENTATION
80 #include "external/stb_rect_pack.h" // Required for: ttf/bdf font rectangles packaging
82 #include <math.h> // Required for: ttf/bdf font rectangles packaging
84 #if defined(__GNUC__) // GCC and Clang
85 #pragma GCC diagnostic pop
86 #endif
87#endif
89#if defined(SUPPORT_FILEFORMAT_TTF)
90 #if defined(__GNUC__) // GCC and Clang
91 #pragma GCC diagnostic push
92 #pragma GCC diagnostic ignored "-Wunused-function"
93 #endif
95 #define STBTT_malloc(x,u) ((void)(u),RL_MALLOC(x))
96 #define STBTT_free(x,u) ((void)(u),RL_FREE(x))
98 #define STBTT_STATIC
99 #define STB_TRUETYPE_IMPLEMENTATION
100 #include "external/stb_truetype.h" // Required for: ttf font data reading
102 #if defined(__GNUC__) // GCC and Clang
103 #pragma GCC diagnostic pop
104 #endif
105#endif
107//----------------------------------------------------------------------------------
108// Defines and Macros
109//----------------------------------------------------------------------------------
110#ifndef MAX_TEXT_BUFFER_LENGTH
111 #define MAX_TEXT_BUFFER_LENGTH 1024 // Size of internal static buffers used on some functions:
112 // TextFormat(), TextSubtext(), TextToUpper(), TextToLower(), TextToPascal(), TextSplit()
113#endif
114#ifndef MAX_TEXT_UNICODE_CHARS
115 #define MAX_TEXT_UNICODE_CHARS 512 // Maximum number of unicode codepoints: GetCodepoints()
116#endif
117#ifndef MAX_TEXTSPLIT_COUNT
118 #define MAX_TEXTSPLIT_COUNT 128 // Maximum number of substrings to split: TextSplit()
119#endif
121//----------------------------------------------------------------------------------
122// Types and Structures Definition
123//----------------------------------------------------------------------------------
124//...
126//----------------------------------------------------------------------------------
127// Global variables
128//----------------------------------------------------------------------------------
129#if defined(SUPPORT_DEFAULT_FONT)
130// Default font provided by raylib
131// NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core]
132static Font defaultFont = { 0 };
133#endif
134static int textLineSpacing = 2; // Text vertical line spacing in pixels (between lines)
136//----------------------------------------------------------------------------------
137// Other Modules Functions Declaration (required by text)
138//----------------------------------------------------------------------------------
139//...
141//----------------------------------------------------------------------------------
142// Module Internal Functions Declaration
143//----------------------------------------------------------------------------------
144#if defined(SUPPORT_FILEFORMAT_FNT)
145static Font LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file)
146#endif
147#if defined(SUPPORT_FILEFORMAT_BDF)
148static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, const int *codepoints, int codepointCount, int *outFontSize);
149#endif
151#if defined(SUPPORT_DEFAULT_FONT)
152extern void LoadFontDefault(void);
153extern void UnloadFontDefault(void);
154#endif
156//----------------------------------------------------------------------------------
157// Module Functions Definition
158//----------------------------------------------------------------------------------
159#if defined(SUPPORT_DEFAULT_FONT)
160// Load raylib default font
161extern void LoadFontDefault(void)
162{
163 #define BIT_CHECK(a,b) ((a) & (1u << (b)))
165 // Check to see if we have already allocated the font for an image, and if we don't need to upload, then just return
166 if (defaultFont.glyphs != NULL) return;
168 // NOTE: Using UTF-8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement
169 // REF: http://www.utf8-chartable.de/unicode-utf8-table.pl
171 defaultFont.glyphCount = 224; // Number of glyphs included in our default font
172 defaultFont.glyphPadding = 0; // Characters padding
174 // Default font is directly defined here (data generated from a sprite font image)
175 // This way, we reconstruct Font without creating large global variables
176 // This data is automatically allocated to Stack and automatically deallocated at the end of this function
177 unsigned int defaultFontData[512] = {
178 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f,
179 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de,
180 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f,
181 0x00200000, 0x8001b000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
182 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xc0000fbe, 0xfbf7e00f, 0x5fbf7e7d, 0x0050bee8, 0x440808a2, 0x0a142fe8, 0x50810285, 0x0050a048,
183 0x49e428a2, 0x0a142828, 0x40810284, 0x0048a048, 0x10020fbe, 0x09f7ebaf, 0xd89f3e84, 0x0047a04f, 0x09e48822, 0x0a142aa1, 0x50810284, 0x0048a048,
184 0x04082822, 0x0a142fa0, 0x50810285, 0x0050a248, 0x00008fbe, 0xfbf42021, 0x5f817e7d, 0x07d09ce8, 0x00008000, 0x00000fe0, 0x00000000, 0x00000000,
185 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000c0180,
186 0xdfbf4282, 0x0bfbf7ef, 0x42850505, 0x004804bf, 0x50a142c6, 0x08401428, 0x42852505, 0x00a808a0, 0x50a146aa, 0x08401428, 0x42852505, 0x00081090,
187 0x5fa14a92, 0x0843f7e8, 0x7e792505, 0x00082088, 0x40a15282, 0x08420128, 0x40852489, 0x00084084, 0x40a16282, 0x0842022a, 0x40852451, 0x00088082,
188 0xc0bf4282, 0xf843f42f, 0x7e85fc21, 0x3e0900bf, 0x00000000, 0x00000004, 0x00000000, 0x000c0180, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
189 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04000402, 0x41482000, 0x00000000, 0x00000800,
190 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820,
191 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0,
192 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
193 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000f01, 0x00000000, 0x06000000, 0x24000000, 0x00000f01, 0x00000000, 0x09108000,
194 0x24fa28a2, 0x00000f01, 0x00000000, 0x013e0000, 0x2242252a, 0x00000f52, 0x00000000, 0x038a8000, 0x2422222a, 0x00000f29, 0x00000000, 0x010a8000,
195 0x2412252a, 0x00000f01, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000f01, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000,
196 0x0003e000, 0x00000f00, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03,
197 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202,
198 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002,
199 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002,
200 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002,
201 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010,
202 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7,
203 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
204 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a,
205 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b,
206 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008,
207 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210,
208 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe,
209 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2,
210 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000,
211 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000,
212 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000,
213 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000,
214 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
215 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
216 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
217 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
218 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
219 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
220 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 };
222 int charsHeight = 10;
223 int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically
225 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,
226 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,
227 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,
228 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,
229 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,
230 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,
231 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 };
233 // Re-construct image from defaultFontData and generate OpenGL texture
234 //----------------------------------------------------------------------
235 Image imFont = {
236 .data = RL_CALLOC(128*128, 2), // 2 bytes per pixel (gray + alpha)
237 .width = 128,
238 .height = 128,
239 .mipmaps = 1,
240 .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA
241 };
243 // Fill image.data with defaultFontData (convert from bit to pixel!)
244 for (int i = 0, counter = 0; i < imFont.width*imFont.height; i += 32)
245 {
246 for (int j = 31; j >= 0; j--)
247 {
248 if (BIT_CHECK(defaultFontData[counter], j))
249 {
250 // NOTE: We are unreferencing data as short, so,
251 // we must consider data as little-endian order (alpha + gray)
252 ((unsigned short *)imFont.data)[i + j] = 0xffff;
253 }
254 else
255 {
256 ((unsigned char *)imFont.data)[(i + j)*sizeof(short)] = 0xff;
257 ((unsigned char *)imFont.data)[(i + j)*sizeof(short) + 1] = 0x00;
258 }
259 }
261 counter++;
262 }
264 defaultFont.texture = LoadTextureFromImage(imFont);
266 // we have already loaded the font glyph data an image, and the GPU is ready, we are done
267 // if we don't do this, we will leak memory by reallocating the glyphs and rects
268 if (defaultFont.glyphs != NULL)
269 {
270 UnloadImage(imFont);
271 return;
272 }
274 // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, glyphCount
275 //------------------------------------------------------------------------------
277 // Allocate space for our characters info data
278 // NOTE: This memory must be freed at end! --> Done by CloseWindow()
279 defaultFont.glyphs = (GlyphInfo *)RL_CALLOC(defaultFont.glyphCount, sizeof(GlyphInfo));
280 defaultFont.recs = (Rectangle *)RL_CALLOC(defaultFont.glyphCount, sizeof(Rectangle));
282 int currentLine = 0;
283 int currentPosX = charsDivisor;
284 int testPosX = charsDivisor;
286 for (int i = 0; i < defaultFont.glyphCount; i++)
287 {
288 defaultFont.glyphs[i].value = 32 + i; // First char is 32
290 defaultFont.recs[i].x = (float)currentPosX;
291 defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
292 defaultFont.recs[i].width = (float)charsWidth[i];
293 defaultFont.recs[i].height = (float)charsHeight;
295 testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor);
297 if (testPosX >= imFont.width)
298 {
299 currentLine++;
300 currentPosX = 2*charsDivisor + charsWidth[i];
301 testPosX = currentPosX;
303 defaultFont.recs[i].x = (float)charsDivisor;
304 defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
305 }
306 else currentPosX = testPosX;
308 // NOTE: On default font character offsets and xAdvance are not required
309 defaultFont.glyphs[i].offsetX = 0;
310 defaultFont.glyphs[i].offsetY = 0;
311 defaultFont.glyphs[i].advanceX = 0;
313 // Fill character image data from fontClear data
314 defaultFont.glyphs[i].image = ImageFromImage(imFont, defaultFont.recs[i]);
315 }
317 UnloadImage(imFont);
319 defaultFont.baseSize = (int)defaultFont.recs[0].height;
321 TRACELOG(LOG_INFO, "FONT: Default font loaded successfully (%i glyphs)", defaultFont.glyphCount);
322}
324// Unload raylib default font
325extern void UnloadFontDefault(void)
326{
327 for (int i = 0; i < defaultFont.glyphCount; i++) UnloadImage(defaultFont.glyphs[i].image);
328 UnloadTexture(defaultFont.texture);
329 RL_FREE(defaultFont.glyphs);
330 RL_FREE(defaultFont.recs);
331 defaultFont.glyphCount = 0;
332 defaultFont.glyphs = NULL;
333 defaultFont.recs = NULL;
334}
335#endif // SUPPORT_DEFAULT_FONT
337// Get the default font, useful to be used with extended parameters
338Font GetFontDefault()
339{
340#if defined(SUPPORT_DEFAULT_FONT)
341 return defaultFont;
342#else
343 Font font = { 0 };
344 return font;
345#endif
346}
348// Load Font from file into GPU memory (VRAM)
349Font LoadFont(const char *fileName)
350{
351 // Default values for ttf font generation
352#ifndef FONT_TTF_DEFAULT_SIZE
353 #define FONT_TTF_DEFAULT_SIZE 32 // TTF font generation default char size (char-height)
354#endif
355#ifndef FONT_TTF_DEFAULT_NUMCHARS
356 #define FONT_TTF_DEFAULT_NUMCHARS 95 // TTF font generation default charset: 95 glyphs (ASCII 32..126)
357#endif
358#ifndef FONT_TTF_DEFAULT_FIRST_CHAR
359 #define FONT_TTF_DEFAULT_FIRST_CHAR 32 // TTF font generation default first char for image sprite font (32-Space)
360#endif
361#ifndef FONT_TTF_DEFAULT_CHARS_PADDING
362 #define FONT_TTF_DEFAULT_CHARS_PADDING 4 // TTF font generation default glyphs padding
363#endif
365 Font font = { 0 };
367#if defined(SUPPORT_FILEFORMAT_TTF)
368 if (IsFileExtension(fileName, ".ttf") || IsFileExtension(fileName, ".otf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS);
369 else
370#endif
371#if defined(SUPPORT_FILEFORMAT_FNT)
372 if (IsFileExtension(fileName, ".fnt")) font = LoadBMFont(fileName);
373 else
374#endif
375#if defined(SUPPORT_FILEFORMAT_BDF)
376 if (IsFileExtension(fileName, ".bdf")) font = LoadFontEx(fileName, FONT_TTF_DEFAULT_SIZE, NULL, FONT_TTF_DEFAULT_NUMCHARS);
377 else
378#endif
379 {
380 Image image = LoadImage(fileName);
381 if (image.data != NULL) font = LoadFontFromImage(image, MAGENTA, FONT_TTF_DEFAULT_FIRST_CHAR);
382 else font = GetFontDefault();
383 UnloadImage(image);
384 }
386 if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName);
387 else
388 {
389 SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance)
390 TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount);
391 }
393 return font;
394}
396// Load Font from TTF or BDF font file with generation parameters
397// NOTE: You can pass an array with desired characters, those characters should be available in the font
398// if array is NULL, default char set is selected 32..126
399Font LoadFontEx(const char *fileName, int fontSize, const int *codepoints, int codepointCount)
400{
401 Font font = { 0 };
403 // Loading file to memory
404 int dataSize = 0;
405 unsigned char *fileData = LoadFileData(fileName, &dataSize);
407 if (fileData != NULL)
408 {
409 // Loading font from memory data
410 font = LoadFontFromMemory(GetFileExtension(fileName), fileData, dataSize, fontSize, codepoints, codepointCount);
412 UnloadFileData(fileData);
413 }
415 return font;
416}
418// Load an Image font file (XNA style)
419Font LoadFontFromImage(Image image, Color key, int firstChar)
420{
421#ifndef MAX_GLYPHS_FROM_IMAGE
422 #define MAX_GLYPHS_FROM_IMAGE 256 // Maximum number of glyphs supported on image scan
423#endif
425 #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r) && (col1.g == col2.g) && (col1.b == col2.b) && (col1.a == col2.a))
427 Font font = GetFontDefault();
429 int charSpacing = 0;
430 int lineSpacing = 0;
432 int x = 0;
433 int y = 0;
435 // We allocate a temporal arrays for glyphs data measures,
436 // once we get the actual number of glyphs, we copy data to a sized arrays
437 int tempCharValues[MAX_GLYPHS_FROM_IMAGE] = { 0 };
438 Rectangle tempCharRecs[MAX_GLYPHS_FROM_IMAGE] = { 0 };
440 Color *pixels = LoadImageColors(image);
442 // Parse image data to get charSpacing and lineSpacing
443 for (y = 0; y < image.height; y++)
444 {
445 for (x = 0; x < image.width; x++)
446 {
447 if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
448 }
450 if (!COLOR_EQUAL(pixels[y*image.width + x], key)) break;
451 }
453 if ((x == 0) || (y == 0)) return font; // Security check
455 charSpacing = x;
456 lineSpacing = y;
458 int charHeight = 0;
459 int j = 0;
461 while (!COLOR_EQUAL(pixels[(lineSpacing + j)*image.width + charSpacing], key)) j++;
463 charHeight = j;
465 // Check array values to get characters: value, x, y, w, h
466 int index = 0;
467 int lineToRead = 0;
468 int xPosToRead = charSpacing;
470 // Parse image data to get rectangle sizes
471 while ((lineSpacing + lineToRead*(charHeight + lineSpacing)) < image.height)
472 {
473 while ((xPosToRead < image.width) &&
474 !COLOR_EQUAL((pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead]), key))
475 {
476 tempCharValues[index] = firstChar + index;
478 tempCharRecs[index].x = (float)xPosToRead;
479 tempCharRecs[index].y = (float)(lineSpacing + lineToRead*(charHeight + lineSpacing));
480 tempCharRecs[index].height = (float)charHeight;
482 int charWidth = 0;
484 while (!COLOR_EQUAL(pixels[(lineSpacing + (charHeight+lineSpacing)*lineToRead)*image.width + xPosToRead + charWidth], key)) charWidth++;
486 tempCharRecs[index].width = (float)charWidth;
488 index++;
490 xPosToRead += (charWidth + charSpacing);
491 }
493 lineToRead++;
494 xPosToRead = charSpacing;
495 }
497 // NOTE: We need to remove key color borders from image to avoid weird
498 // artifacts on texture scaling when using TEXTURE_FILTER_BILINEAR or TEXTURE_FILTER_TRILINEAR
499 for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK;
501 // Create a new image with the processed color data (key color replaced by BLANK)
502 Image fontClear = {
503 .data = pixels,
504 .width = image.width,
505 .height = image.height,
506 .mipmaps = 1,
507 .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8
508 };
510 // Set font with all data parsed from image
511 font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture
512 font.glyphCount = index;
513 font.glyphPadding = 0;
515 // We got tempCharValues and tempCharsRecs populated with glyphs data
516 // Now we move temp data to sized charValues and charRecs arrays
517 font.glyphs = (GlyphInfo *)RL_MALLOC(font.glyphCount*sizeof(GlyphInfo));
518 font.recs = (Rectangle *)RL_MALLOC(font.glyphCount*sizeof(Rectangle));
520 for (int i = 0; i < font.glyphCount; i++)
521 {
522 font.glyphs[i].value = tempCharValues[i];
524 // Get character rectangle in the font atlas texture
525 font.recs[i] = tempCharRecs[i];
527 // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0)
528 font.glyphs[i].offsetX = 0;
529 font.glyphs[i].offsetY = 0;
530 font.glyphs[i].advanceX = 0;
532 // Fill character image data from fontClear data
533 font.glyphs[i].image = ImageFromImage(fontClear, tempCharRecs[i]);
534 }
536 UnloadImage(fontClear); // Unload processed image once converted to texture
538 font.baseSize = (int)font.recs[0].height;
540 return font;
541}
543// Load font from memory buffer, fileType refers to extension: i.e. ".ttf"
544Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount)
545{
546 Font font = { 0 };
548 char fileExtLower[16] = { 0 };
549 strncpy(fileExtLower, TextToLower(fileType), 16 - 1);
551 font.baseSize = fontSize;
552 font.glyphPadding = 0;
554#if defined(SUPPORT_FILEFORMAT_TTF)
555 if (TextIsEqual(fileExtLower, ".ttf") ||
556 TextIsEqual(fileExtLower, ".otf"))
557 {
558 font.glyphs = LoadFontData(fileData, dataSize, font.baseSize, codepoints, (codepointCount > 0)? codepointCount : 95, FONT_DEFAULT, &font.glyphCount);
559 }
560 else
561#endif
562#if defined(SUPPORT_FILEFORMAT_BDF)
563 if (TextIsEqual(fileExtLower, ".bdf"))
564 {
565 font.glyphs = LoadFontDataBDF(fileData, dataSize, codepoints, (codepointCount > 0)? codepointCount : 95, &font.baseSize);
566 font.glyphCount = (codepointCount > 0)? codepointCount : 95;
567 }
568 else
569#endif
570 {
571 font.glyphs = NULL;
572 }
574#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
575 if (font.glyphs != NULL)
576 {
577 font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING;
579 Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0);
580 font.texture = LoadTextureFromImage(atlas);
582 // Update glyphs[i].image to use alpha, required to be used on ImageDrawText()
583 for (int i = 0; i < font.glyphCount; i++)
584 {
585 UnloadImage(font.glyphs[i].image);
586 font.glyphs[i].image = ImageFromImage(atlas, font.recs[i]);
587 }
589 UnloadImage(atlas);
591 TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", font.baseSize, font.glyphCount);
592 }
593 else font = GetFontDefault();
594#else
595 font = GetFontDefault();
596#endif
598 return font;
599}
601// Check if a font is valid (font data loaded)
602// WARNING: GPU texture not checked
603bool IsFontValid(Font font)
604{
605 return ((font.baseSize > 0) && // Validate font size
606 (font.glyphCount > 0) && // Validate font contains some glyph
607 (font.recs != NULL) && // Validate font recs defining glyphs on texture atlas
608 (font.glyphs != NULL)); // Validate glyph data is loaded
610 // NOTE: Further validations could be done to verify if recs and glyphs contain valid data (glyphs values, metrics...)
611}
613// Load font data for further use
614// NOTE: Requires TTF font memory data and can generate SDF data
615GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount, int type, int *glyphCount)
616{
617 // NOTE: Using some SDF generation default values,
618 // trades off precision with ability to handle *smaller* sizes
619#ifndef FONT_SDF_CHAR_PADDING
620 #define FONT_SDF_CHAR_PADDING 4 // SDF font generation char padding
621#endif
622#ifndef FONT_SDF_ON_EDGE_VALUE
623 #define FONT_SDF_ON_EDGE_VALUE 128 // SDF font generation on edge value
624#endif
625#ifndef FONT_SDF_PIXEL_DIST_SCALE
626 #define FONT_SDF_PIXEL_DIST_SCALE 64.0f // SDF font generation pixel distance scale
627#endif
628#ifndef FONT_BITMAP_ALPHA_THRESHOLD
629 #define FONT_BITMAP_ALPHA_THRESHOLD 80 // Bitmap (B&W) font generation alpha threshold
630#endif
632 GlyphInfo *glyphs = NULL;
633 int glyphCounter = 0;
635#if defined(SUPPORT_FILEFORMAT_TTF)
636 // Load font data (including pixel data) from TTF memory file
637 // NOTE: Loaded information should be enough to generate font image atlas, using any packaging method
638 if (fileData != NULL)
639 {
640 bool genFontChars = false;
641 stbtt_fontinfo fontInfo = { 0 };
642 int *requiredCodepoints = (int *)codepoints; // TODO: Should we create a shallow copy to avoid "dealing" with a const user array?
644 if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Initialize font for data reading
645 {
646 // Calculate font scale factor
647 float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize);
649 // Calculate font basic metrics
650 // NOTE: ascent is equivalent to font baseline
651 int ascent = 0;
652 int descent = 0;
653 int lineGap = 0;
654 stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
656 // In case no chars count provided, default to 95
657 codepointCount = (codepointCount > 0)? codepointCount : 95;
659 // Fill fontChars in case not provided externally
660 // NOTE: By default we fill glyphCount consecutively, starting at 32 (Space)
661 if (requiredCodepoints == NULL)
662 {
663 requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int));
664 for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = i + 32;
665 genFontChars = true;
666 }
668 // Check available glyphs on provided font before loading them
669 for (int i = 0, index; i < codepointCount; i++)
670 {
671 index = stbtt_FindGlyphIndex(&fontInfo, requiredCodepoints[i]);
672 if (index > 0) glyphCounter++;
673 }
675 // WARNING: Allocating space for maximum number of codepoints
676 glyphs = (GlyphInfo *)RL_CALLOC(glyphCounter, sizeof(GlyphInfo));
677 glyphCounter = 0; // Reset to reuse
679 int k = 0;
680 for (int i = 0; i < codepointCount; i++)
681 {
682 int cpWidth = 0, cpHeight = 0; // Codepoint width and height (on generation)
683 int cp = requiredCodepoints[i]; // Codepoint value to get info for
685 // Render a unicode codepoint to a bitmap
686 // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap
687 // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be
688 // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide
690 // Check if a glyph is available in the font
691 // WARNING: if (index == 0), glyph not found, it could fallback to default .notdef glyph (if defined in font)
692 int index = stbtt_FindGlyphIndex(&fontInfo, cp);
694 if (index > 0)
695 {
696 // NOTE: Only storing glyphs for codepoints found in the font
697 glyphs[k].value = cp;
699 switch (type)
700 {
701 case FONT_DEFAULT:
702 case FONT_BITMAP:
703 {
704 glyphs[k].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, cp,
705 &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY);
706 } break;
707 case FONT_SDF:
708 {
709 if (cp != 32)
710 {
711 glyphs[k].image.data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, cp,
712 FONT_SDF_CHAR_PADDING, FONT_SDF_ON_EDGE_VALUE, FONT_SDF_PIXEL_DIST_SCALE,
713 &cpWidth, &cpHeight, &glyphs[k].offsetX, &glyphs[k].offsetY);
714 }
715 } break;
716 //case FONT_MSDF:
717 default: break;
718 }
720 if (glyphs[k].image.data != NULL) // Glyph data has been found in the font
721 {
722 stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL);
723 glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor);
725 // WARNING: If requested SDF font, sdf-glyph height is definitely bigger than fontSize due to FONT_SDF_CHAR_PADDING
726 if ((type != FONT_SDF) && (cpHeight > fontSize)) TRACELOG(LOG_WARNING, "FONT: [0x%04x] Glyph height is bigger than requested font size: %i > %i", cp, cpHeight, (int)fontSize);
728 // Load glyph image
729 glyphs[k].image.width = cpWidth;
730 glyphs[k].image.height = cpHeight;
731 glyphs[k].image.mipmaps = 1;
732 glyphs[k].image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
734 glyphs[k].offsetY += (int)((float)ascent*scaleFactor);
735 }
736 //else TRACELOG(LOG_WARNING, "FONT: Glyph [0x%08x] has no image data available", cp); // Only reported for 0x20 and 0x3000
738 // We create an empty image for Space character (0x20), useful for sprite font generation
739 // NOTE: Another space to consider: 0x3000 (CJK - Ideographic Space)
740 if ((cp == 0x20) || (cp == 0x3000))
741 {
742 stbtt_GetCodepointHMetrics(&fontInfo, cp, &glyphs[k].advanceX, NULL);
743 glyphs[k].advanceX = (int)((float)glyphs[k].advanceX*scaleFactor);
745 Image imSpace = {
746 .data = RL_CALLOC(glyphs[k].advanceX*fontSize, 2),
747 .width = glyphs[k].advanceX,
748 .height = fontSize,
749 .mipmaps = 1,
750 .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE
751 };
753 glyphs[k].image = imSpace;
754 }
756 if (type == FONT_BITMAP)
757 {
758 // Aliased bitmap (black & white) font generation, avoiding anti-aliasing
759 // NOTE: For optimum results, bitmap font should be generated at base pixel size
760 for (int p = 0; p < cpWidth*cpHeight; p++)
761 {
762 if (((unsigned char *)glyphs[k].image.data)[p] < FONT_BITMAP_ALPHA_THRESHOLD)
763 ((unsigned char *)glyphs[k].image.data)[p] = 0;
764 else ((unsigned char *)glyphs[k].image.data)[p] = 255;
765 }
766 }
768 k++;
769 glyphCounter++;
770 }
771 else
772 {
773 // WARNING: Glyph not found on font, optionally use a fallback glyph
774 }
775 }
777 if (glyphCounter < codepointCount) TRACELOG(LOG_WARNING, "FONT: Requested codepoints glyphs found: [%i/%i]", k, codepointCount);
778 }
779 else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data");
781 if (genFontChars) RL_FREE(requiredCodepoints);
782 }
783#endif
785 *glyphCount = glyphCounter;
786 return glyphs;
787}
789// Generate image font atlas using chars info
790// NOTE: Packing method: 0-Default, 1-Skyline
791#if defined(SUPPORT_FILEFORMAT_TTF) || defined(SUPPORT_FILEFORMAT_BDF)
792Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod)
793{
794 Image atlas = { 0 };
796 if (glyphs == NULL)
797 {
798 TRACELOG(LOG_WARNING, "FONT: Provided chars info not valid, returning empty image atlas");
799 return atlas;
800 }
802 *glyphRecs = NULL;
804 // In case no chars count provided we suppose default of 95
805 glyphCount = (glyphCount > 0)? glyphCount : 95;
807 // NOTE: Rectangles memory is loaded here!
808 Rectangle *recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle));
810 // Calculate image size based on total glyph width and glyph row count
811 int totalWidth = 0;
812 int maxGlyphWidth = 0;
814 for (int i = 0; i < glyphCount; i++)
815 {
816 if (glyphs[i].image.width > maxGlyphWidth) maxGlyphWidth = glyphs[i].image.width;
817 totalWidth += glyphs[i].image.width + 2*padding;
818 }
820//#define SUPPORT_FONT_ATLAS_SIZE_CONSERVATIVE
821#if defined(SUPPORT_FONT_ATLAS_SIZE_CONSERVATIVE)
822 int rowCount = 0;
823 int imageSize = 64; // Define minimum starting value to avoid unnecessary calculation steps for very small images
825 // NOTE: maxGlyphWidth is maximum possible space left at the end of row
826 while (totalWidth > (imageSize - maxGlyphWidth)*rowCount)
827 {
828 imageSize *= 2; // Double the size of image (to keep POT)
829 rowCount = imageSize/(fontSize + 2*padding); // Calculate new row count for the new image size
830 }
832 atlas.width = imageSize; // Atlas bitmap width
833 atlas.height = imageSize; // Atlas bitmap height
834#else
835 int paddedFontSize = fontSize + 2*padding;
837 // No need for a so-conservative atlas generation
838 // NOTE: Multiplying total expected are by 1.2f scale factor
839 float totalArea = totalWidth*paddedFontSize*1.2f;
840 float imageMinSize = sqrtf(totalArea);
841 int imageSize = (int)powf(2, ceilf(logf(imageMinSize)/logf(2)));
843 if (totalArea < ((imageSize*imageSize)/2))
844 {
845 atlas.width = imageSize; // Atlas bitmap width
846 atlas.height = imageSize/2; // Atlas bitmap height
847 }
848 else
849 {
850 atlas.width = imageSize; // Atlas bitmap width
851 atlas.height = imageSize; // Atlas bitmap height
852 }
853#endif
855 atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp)
856 atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
857 atlas.mipmaps = 1;
859 // DEBUG: We can see padding in the generated image setting a gray background...
860 //for (int i = 0; i < atlas.width*atlas.height; i++) ((unsigned char *)atlas.data)[i] = 100;
862 if (packMethod == 0) // Use basic packing algorithm
863 {
864 int offsetX = padding;
865 int offsetY = padding;
867 // NOTE: Using simple packaging, one char after another
868 for (int i = 0; i < glyphCount; i++)
869 {
870 // Check remaining space for glyph
871 if (offsetX >= (atlas.width - glyphs[i].image.width - 2*padding))
872 {
873 offsetX = padding;
875 // NOTE: Be careful on offsetY for SDF fonts, by default SDF
876 // use an internal padding of 4 pixels, it means char rectangle
877 // height is bigger than fontSize, it could be up to (fontSize + 8)
878 offsetY += (fontSize + 2*padding);
880 if (offsetY > (atlas.height - fontSize - padding))
881 {
882 for (int j = i + 1; j < glyphCount; j++)
883 {
884 TRACELOG(LOG_WARNING, "FONT: Failed to package character (0x%02x)", glyphs[j].value);
885 // Make sure remaining recs contain valid data
886 recs[j].x = 0;
887 recs[j].y = 0;
888 recs[j].width = 0;
889 recs[j].height = 0;
890 }
891 break; // Break for() loop, stop processing glyphs
892 }
893 }
895 // Copy pixel data from glyph image to atlas
896 for (int y = 0; y < glyphs[i].image.height; y++)
897 {
898 for (int x = 0; x < glyphs[i].image.width; x++)
899 {
900 ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x];
901 }
902 }
904 // Fill chars rectangles in atlas info
905 recs[i].x = (float)offsetX;
906 recs[i].y = (float)offsetY;
907 recs[i].width = (float)glyphs[i].image.width;
908 recs[i].height = (float)glyphs[i].image.height;
910 // Move atlas position X for next character drawing
911 offsetX += (glyphs[i].image.width + 2*padding);
912 }
913 }
914 else if (packMethod == 1) // Use Skyline rect packing algorithm (stb_pack_rect)
915 {
916 stbrp_context *context = (stbrp_context *)RL_MALLOC(sizeof(*context));
917 stbrp_node *nodes = (stbrp_node *)RL_MALLOC(glyphCount*sizeof(*nodes));
919 stbrp_init_target(context, atlas.width, atlas.height, nodes, glyphCount);
920 stbrp_rect *rects = (stbrp_rect *)RL_MALLOC(glyphCount*sizeof(stbrp_rect));
922 // Fill rectangles for packaging
923 for (int i = 0; i < glyphCount; i++)
924 {
925 rects[i].id = i;
926 rects[i].w = glyphs[i].image.width + 2*padding;
927 rects[i].h = glyphs[i].image.height + 2*padding;
928 }
930 // Package rectangles into atlas
931 stbrp_pack_rects(context, rects, glyphCount);
933 for (int i = 0; i < glyphCount; i++)
934 {
935 // It returns char rectangles in atlas
936 recs[i].x = rects[i].x + (float)padding;
937 recs[i].y = rects[i].y + (float)padding;
938 recs[i].width = (float)glyphs[i].image.width;
939 recs[i].height = (float)glyphs[i].image.height;
941 if (rects[i].was_packed)
942 {
943 // Copy pixel data from fc.data to atlas
944 for (int y = 0; y < glyphs[i].image.height; y++)
945 {
946 for (int x = 0; x < glyphs[i].image.width; x++)
947 {
948 ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = ((unsigned char *)glyphs[i].image.data)[y*glyphs[i].image.width + x];
949 }
950 }
951 }
952 else TRACELOG(LOG_WARNING, "FONT: Failed to package character (0x%02x)", glyphs[i].value);
953 }
955 RL_FREE(rects);
956 RL_FREE(nodes);
957 RL_FREE(context);
958 }
960#if defined(SUPPORT_FONT_ATLAS_WHITE_REC)
961 // Add a 3x3 white rectangle at the bottom-right corner of the generated atlas,
962 // useful to use as the white texture to draw shapes with raylib, using this rectangle
963 // shapes and text can be backed into a single draw call: SetShapesTexture()
964 for (int i = 0, k = atlas.width*atlas.height - 1; i < 3; i++)
965 {
966 ((unsigned char *)atlas.data)[k - 0] = 255;
967 ((unsigned char *)atlas.data)[k - 1] = 255;
968 ((unsigned char *)atlas.data)[k - 2] = 255;
969 k -= atlas.width;
970 }
971#endif
973 // Convert image data from GRAYSCALE to GRAY_ALPHA
974 unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels
976 for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2)
977 {
978 dataGrayAlpha[k] = 255;
979 dataGrayAlpha[k + 1] = ((unsigned char *)atlas.data)[i];
980 }
982 RL_FREE(atlas.data);
983 atlas.data = dataGrayAlpha;
984 atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA;
986 *glyphRecs = recs;
988 return atlas;
989}
990#endif
992// Unload font glyphs info data (RAM)
993void UnloadFontData(GlyphInfo *glyphs, int glyphCount)
994{
995 if (glyphs != NULL)
996 {
997 for (int i = 0; i < glyphCount; i++) UnloadImage(glyphs[i].image);
999 RL_FREE(glyphs);
1000 }
1001}
1003// Unload Font from GPU memory (VRAM)
1004void UnloadFont(Font font)
1005{
1006 // NOTE: Make sure font is not default font (fallback)
1007 if (font.texture.id != GetFontDefault().texture.id)
1008 {
1009 UnloadFontData(font.glyphs, font.glyphCount);
1010 UnloadTexture(font.texture);
1011 RL_FREE(font.recs);
1013 TRACELOGD("FONT: Unloaded font data from RAM and VRAM");
1014 }
1015}
1017// Export font as code file, returns true on success
1018bool ExportFontAsCode(Font font, const char *fileName)
1019{
1020 bool success = false;
1022#ifndef TEXT_BYTES_PER_LINE
1023 #define TEXT_BYTES_PER_LINE 20
1024#endif
1026 #define MAX_FONT_DATA_SIZE 1024*1024 // 1 MB
1028 // Get file name from path
1029 char fileNamePascal[256] = { 0 };
1030 strncpy(fileNamePascal, TextToPascal(GetFileNameWithoutExt(fileName)), 256 - 1);
1032 // NOTE: Text data buffer size is estimated considering image data size in bytes
1033 // and requiring 6 char bytes for every byte: "0x00, "
1034 char *txtData = (char *)RL_CALLOC(MAX_FONT_DATA_SIZE, sizeof(char));
1036 int byteCount = 0;
1037 byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n");
1038 byteCount += sprintf(txtData + byteCount, "// //\n");
1039 byteCount += sprintf(txtData + byteCount, "// FontAsCode exporter v1.0 - Font data exported as an array of bytes //\n");
1040 byteCount += sprintf(txtData + byteCount, "// //\n");
1041 byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n");
1042 byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n");
1043 byteCount += sprintf(txtData + byteCount, "// //\n");
1044 byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n");
1045 byteCount += sprintf(txtData + byteCount, "// //\n");
1046 byteCount += sprintf(txtData + byteCount, "// ---------------------------------------------------------------------------------- //\n");
1047 byteCount += sprintf(txtData + byteCount, "// //\n");
1048 byteCount += sprintf(txtData + byteCount, "// TODO: Fill the information and license of the exported font here: //\n");
1049 byteCount += sprintf(txtData + byteCount, "// //\n");
1050 byteCount += sprintf(txtData + byteCount, "// Font name: .... //\n");
1051 byteCount += sprintf(txtData + byteCount, "// Font creator: .... //\n");
1052 byteCount += sprintf(txtData + byteCount, "// Font LICENSE: .... //\n");
1053 byteCount += sprintf(txtData + byteCount, "// //\n");
1054 byteCount += sprintf(txtData + byteCount, "////////////////////////////////////////////////////////////////////////////////////////\n\n");
1056 // Support font export and initialization
1057 // NOTE: This mechanism is highly coupled to raylib
1058 Image image = LoadImageFromTexture(font.texture);
1059 if (image.format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) TRACELOG(LOG_WARNING, "Font export as code: Font image format is not GRAY+ALPHA!");
1060 int imageDataSize = GetPixelDataSize(image.width, image.height, image.format);
1062 // Image data is usually GRAYSCALE + ALPHA and can be reduced to GRAYSCALE
1063 //ImageFormat(&image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE);
1065#define SUPPORT_COMPRESSED_FONT_ATLAS
1066#if defined(SUPPORT_COMPRESSED_FONT_ATLAS)
1067 // WARNING: Data is compressed using raylib CompressData() DEFLATE,
1068 // it requires to be decompressed with raylib DecompressData(), that requires
1069 // compiling raylib with SUPPORT_COMPRESSION_API config flag enabled
1071 // Compress font image data
1072 int compDataSize = 0;
1073 unsigned char *compData = CompressData((const unsigned char *)image.data, imageDataSize, &compDataSize);
1075 // Save font image data (compressed)
1076 byteCount += sprintf(txtData + byteCount, "#define COMPRESSED_DATA_SIZE_FONT_%s %i\n\n", TextToUpper(fileNamePascal), compDataSize);
1077 byteCount += sprintf(txtData + byteCount, "// Font image pixels data compressed (DEFLATE)\n");
1078 byteCount += sprintf(txtData + byteCount, "// NOTE: Original pixel data simplified to GRAYSCALE\n");
1079 byteCount += sprintf(txtData + byteCount, "static unsigned char fontData_%s[COMPRESSED_DATA_SIZE_FONT_%s] = { ", fileNamePascal, TextToUpper(fileNamePascal));
1080 for (int i = 0; i < compDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), compData[i]);
1081 byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", compData[compDataSize - 1]);
1082 RL_FREE(compData);
1083#else
1084 // Save font image data (uncompressed)
1085 byteCount += sprintf(txtData + byteCount, "// Font image pixels data\n");
1086 byteCount += sprintf(txtData + byteCount, "// NOTE: 2 bytes per pixel, GRAY + ALPHA channels\n");
1087 byteCount += sprintf(txtData + byteCount, "static unsigned char fontImageData_%s[%i] = { ", fileNamePascal, imageDataSize);
1088 for (int i = 0; i < imageDataSize - 1; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%02x,\n " : "0x%02x, "), ((unsigned char *)imFont.data)[i]);
1089 byteCount += sprintf(txtData + byteCount, "0x%02x };\n\n", ((unsigned char *)imFont.data)[imageDataSize - 1]);
1090#endif
1092 // Save font recs data
1093 byteCount += sprintf(txtData + byteCount, "// Font characters rectangles data\n");
1094 byteCount += sprintf(txtData + byteCount, "static Rectangle fontRecs_%s[%i] = {\n", fileNamePascal, font.glyphCount);
1095 for (int i = 0; i < font.glyphCount; i++)
1096 {
1097 byteCount += sprintf(txtData + byteCount, " { %1.0f, %1.0f, %1.0f , %1.0f },\n", font.recs[i].x, font.recs[i].y, font.recs[i].width, font.recs[i].height);
1098 }
1099 byteCount += sprintf(txtData + byteCount, "};\n\n");
1101 // Save font glyphs data
1102 // NOTE: Glyphs image data not saved (grayscale pixels),
1103 // it could be generated from image and recs
1104 byteCount += sprintf(txtData + byteCount, "// Font glyphs info data\n");
1105 byteCount += sprintf(txtData + byteCount, "// NOTE: No glyphs.image data provided\n");
1106 byteCount += sprintf(txtData + byteCount, "static GlyphInfo fontGlyphs_%s[%i] = {\n", fileNamePascal, font.glyphCount);
1107 for (int i = 0; i < font.glyphCount; i++)
1108 {
1109 byteCount += sprintf(txtData + byteCount, " { %i, %i, %i, %i, { 0 }},\n", font.glyphs[i].value, font.glyphs[i].offsetX, font.glyphs[i].offsetY, font.glyphs[i].advanceX);
1110 }
1111 byteCount += sprintf(txtData + byteCount, "};\n\n");
1113 // Custom font loading function
1114 byteCount += sprintf(txtData + byteCount, "// Font loading function: %s\n", fileNamePascal);
1115 byteCount += sprintf(txtData + byteCount, "static Font LoadFont_%s(void)\n{\n", fileNamePascal);
1116 byteCount += sprintf(txtData + byteCount, " Font font = { 0 };\n\n");
1117 byteCount += sprintf(txtData + byteCount, " font.baseSize = %i;\n", font.baseSize);
1118 byteCount += sprintf(txtData + byteCount, " font.glyphCount = %i;\n", font.glyphCount);
1119 byteCount += sprintf(txtData + byteCount, " font.glyphPadding = %i;\n\n", font.glyphPadding);
1120 byteCount += sprintf(txtData + byteCount, " // Custom font loading\n");
1121#if defined(SUPPORT_COMPRESSED_FONT_ATLAS)
1122 byteCount += sprintf(txtData + byteCount, " // NOTE: Compressed font image data (DEFLATE), it requires DecompressData() function\n");
1123 byteCount += sprintf(txtData + byteCount, " int fontDataSize_%s = 0;\n", fileNamePascal);
1124 byteCount += sprintf(txtData + byteCount, " unsigned char *data = DecompressData(fontData_%s, COMPRESSED_DATA_SIZE_FONT_%s, &fontDataSize_%s);\n", fileNamePascal, TextToUpper(fileNamePascal), fileNamePascal);
1125 byteCount += sprintf(txtData + byteCount, " Image imFont = { data, %i, %i, 1, %i };\n\n", image.width, image.height, image.format);
1126#else
1127 byteCount += sprintf(txtData + byteCount, " Image imFont = { fontImageData_%s, %i, %i, 1, %i };\n\n", styleName, image.width, image.height, image.format);
1128#endif
1129 byteCount += sprintf(txtData + byteCount, " // Load texture from image\n");
1130 byteCount += sprintf(txtData + byteCount, " font.texture = LoadTextureFromImage(imFont);\n");
1131#if defined(SUPPORT_COMPRESSED_FONT_ATLAS)
1132 byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n");
1133#endif
1134 // We have two possible mechanisms to assign font.recs and font.glyphs data,
1135 // that data is already available as global arrays, we two options to assign that data:
1136 // - 1. Data copy. This option consumes more memory and Font MUST be unloaded by user, requiring additional code
1137 // - 2. Data assignment. This option consumes less memory and Font MUST NOT be unloaded by user because data is on protected DATA segment
1138//#define SUPPORT_FONT_DATA_COPY
1139#if defined(SUPPORT_FONT_DATA_COPY)
1140 byteCount += sprintf(txtData + byteCount, " // Copy glyph recs data from global fontRecs\n");
1141 byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n");
1142 byteCount += sprintf(txtData + byteCount, " font.recs = (Rectangle *)malloc(font.glyphCount*sizeof(Rectangle));\n");
1143 byteCount += sprintf(txtData + byteCount, " memcpy(font.recs, fontRecs_%s, font.glyphCount*sizeof(Rectangle));\n\n", fileNamePascal);
1145 byteCount += sprintf(txtData + byteCount, " // Copy font glyph info data from global fontChars\n");
1146 byteCount += sprintf(txtData + byteCount, " // NOTE: Required to avoid issues if trying to free font\n");
1147 byteCount += sprintf(txtData + byteCount, " font.glyphs = (GlyphInfo *)malloc(font.glyphCount*sizeof(GlyphInfo));\n");
1148 byteCount += sprintf(txtData + byteCount, " memcpy(font.glyphs, fontGlyphs_%s, font.glyphCount*sizeof(GlyphInfo));\n\n", fileNamePascal);
1149#else
1150 byteCount += sprintf(txtData + byteCount, " // Assign glyph recs and info data directly\n");
1151 byteCount += sprintf(txtData + byteCount, " // WARNING: This font data must not be unloaded\n");
1152 byteCount += sprintf(txtData + byteCount, " font.recs = fontRecs_%s;\n", fileNamePascal);
1153 byteCount += sprintf(txtData + byteCount, " font.glyphs = fontGlyphs_%s;\n\n", fileNamePascal);
1154#endif
1155 byteCount += sprintf(txtData + byteCount, " return font;\n");
1156 byteCount += sprintf(txtData + byteCount, "}\n");
1158 UnloadImage(image);
1160 // NOTE: Text data size exported is determined by '\0' (NULL) character
1161 success = SaveFileText(fileName, txtData);
1163 RL_FREE(txtData);
1165 if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Font as code exported successfully", fileName);
1166 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export font as code", fileName);
1168 return success;
1169}
1171// Draw current FPS
1172// NOTE: Uses default font
1173void DrawFPS(int posX, int posY)
1174{
1175 Color color = LIME; // Good FPS
1176 int fps = GetFPS();
1178 if ((fps < 30) && (fps >= 15)) color = ORANGE; // Warning FPS
1179 else if (fps < 15) color = RED; // Low FPS
1181 DrawText(TextFormat("%2i FPS", fps), posX, posY, 20, color);
1182}
1184// Draw text (using default font)
1185// NOTE: fontSize work like in any drawing program but if fontSize is lower than font-base-size, then font-base-size is used
1186// NOTE: chars spacing is proportional to fontSize
1187void DrawText(const char *text, int posX, int posY, int fontSize, Color color)
1188{
1189 // Check if default font has been loaded
1190 if (GetFontDefault().texture.id != 0)
1191 {
1192 Vector2 position = { (float)posX, (float)posY };
1194 int defaultFontSize = 10; // Default Font chars height in pixel
1195 if (fontSize < defaultFontSize) fontSize = defaultFontSize;
1196 int spacing = fontSize/defaultFontSize;
1198 DrawTextEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, color);
1199 }
1200}
1202// Draw text using Font
1203// NOTE: chars spacing is NOT proportional to fontSize
1204void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint)
1205{
1206 if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font
1208 int size = TextLength(text); // Total size in bytes of the text, scanned by codepoints in loop
1210 float textOffsetY = 0; // Offset between lines (on linebreak '\n')
1211 float textOffsetX = 0.0f; // Offset X to next character to draw
1213 float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor
1215 for (int i = 0; i < size;)
1216 {
1217 // Get next codepoint from byte string and glyph index in font
1218 int codepointByteCount = 0;
1219 int codepoint = GetCodepointNext(&text[i], &codepointByteCount);
1220 int index = GetGlyphIndex(font, codepoint);
1222 if (codepoint == '\n')
1223 {
1224 // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup
1225 textOffsetY += (fontSize + textLineSpacing);
1226 textOffsetX = 0.0f;
1227 }
1228 else
1229 {
1230 if ((codepoint != ' ') && (codepoint != '\t'))
1231 {
1232 DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint);
1233 }
1235 if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing);
1236 else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing);
1237 }
1239 i += codepointByteCount; // Move text bytes counter to next codepoint
1240 }
1241}
1243// Draw text using Font and pro parameters (rotation)
1244void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint)
1245{
1246 rlPushMatrix();
1248 rlTranslatef(position.x, position.y, 0.0f);
1249 rlRotatef(rotation, 0.0f, 0.0f, 1.0f);
1250 rlTranslatef(-origin.x, -origin.y, 0.0f);
1252 DrawTextEx(font, text, (Vector2){ 0.0f, 0.0f }, fontSize, spacing, tint);
1254 rlPopMatrix();
1255}
1257// Draw one character (codepoint)
1258void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint)
1259{
1260 // Character index position in sprite font
1261 // NOTE: In case a codepoint is not available in the font, index returned points to '?'
1262 int index = GetGlyphIndex(font, codepoint);
1263 float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor
1265 // Character destination rectangle on screen
1266 // NOTE: We consider glyphPadding on drawing
1267 Rectangle dstRec = { position.x + font.glyphs[index].offsetX*scaleFactor - (float)font.glyphPadding*scaleFactor,
1268 position.y + font.glyphs[index].offsetY*scaleFactor - (float)font.glyphPadding*scaleFactor,
1269 (font.recs[index].width + 2.0f*font.glyphPadding)*scaleFactor,
1270 (font.recs[index].height + 2.0f*font.glyphPadding)*scaleFactor };
1272 // Character source rectangle from font texture atlas
1273 // NOTE: We consider glyphs padding when drawing, it could be required for outline/glow shader effects
1274 Rectangle srcRec = { font.recs[index].x - (float)font.glyphPadding, font.recs[index].y - (float)font.glyphPadding,
1275 font.recs[index].width + 2.0f*font.glyphPadding, font.recs[index].height + 2.0f*font.glyphPadding };
1277 // Draw the character texture on the screen
1278 DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint);
1279}
1281// Draw multiple character (codepoints)
1282void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint)
1283{
1284 float textOffsetY = 0; // Offset between lines (on linebreak '\n')
1285 float textOffsetX = 0.0f; // Offset X to next character to draw
1287 float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor
1289 for (int i = 0; i < codepointCount; i++)
1290 {
1291 int index = GetGlyphIndex(font, codepoints[i]);
1293 if (codepoints[i] == '\n')
1294 {
1295 // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup
1296 textOffsetY += (fontSize + textLineSpacing);
1297 textOffsetX = 0.0f;
1298 }
1299 else
1300 {
1301 if ((codepoints[i] != ' ') && (codepoints[i] != '\t'))
1302 {
1303 DrawTextCodepoint(font, codepoints[i], (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint);
1304 }
1306 if (font.glyphs[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing);
1307 else textOffsetX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing);
1308 }
1309 }
1310}
1312// Set vertical line spacing when drawing with line-breaks
1313void SetTextLineSpacing(int spacing)
1314{
1315 textLineSpacing = spacing;
1316}
1318// Measure string width for default font
1319int MeasureText(const char *text, int fontSize)
1320{
1321 Vector2 textSize = { 0.0f, 0.0f };
1323 // Check if default font has been loaded
1324 if (GetFontDefault().texture.id != 0)
1325 {
1326 int defaultFontSize = 10; // Default Font glyphs height in pixel
1327 if (fontSize < defaultFontSize) fontSize = defaultFontSize;
1328 int spacing = fontSize/defaultFontSize;
1330 textSize = MeasureTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing);
1331 }
1333 return (int)textSize.x;
1334}
1336// Measure string size for Font
1337Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing)
1338{
1339 Vector2 textSize = { 0 };
1341 if ((font.texture.id == 0) || (text == NULL) || (text[0] == '\0')) return textSize; // Security check
1343 int size = TextLength(text); // Get size in bytes of text
1344 int tempByteCounter = 0; // Used to count longer text line num chars
1345 int byteCounter = 0;
1347 float textWidth = 0.0f;
1348 float tempTextWidth = 0.0f; // Used to count longer text line width
1350 float textHeight = fontSize;
1351 float scaleFactor = fontSize/(float)font.baseSize;
1353 int letter = 0; // Current character
1354 int index = 0; // Index position in sprite font
1356 for (int i = 0; i < size;)
1357 {
1358 byteCounter++;
1360 int codepointByteCount = 0;
1361 letter = GetCodepointNext(&text[i], &codepointByteCount);
1362 index = GetGlyphIndex(font, letter);
1364 i += codepointByteCount;
1366 if (letter != '\n')
1367 {
1368 if (font.glyphs[index].advanceX > 0) textWidth += font.glyphs[index].advanceX;
1369 else textWidth += (font.recs[index].width + font.glyphs[index].offsetX);
1370 }
1371 else
1372 {
1373 if (tempTextWidth < textWidth) tempTextWidth = textWidth;
1374 byteCounter = 0;
1375 textWidth = 0;
1377 // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup
1378 textHeight += (fontSize + textLineSpacing);
1379 }
1381 if (tempByteCounter < byteCounter) tempByteCounter = byteCounter;
1382 }
1384 if (tempTextWidth < textWidth) tempTextWidth = textWidth;
1386 textSize.x = tempTextWidth*scaleFactor + (float)((tempByteCounter - 1)*spacing);
1387 textSize.y = textHeight;
1389 return textSize;
1390}
1392// Get index position for a unicode character on font
1393// NOTE: If codepoint is not found in the font it fallbacks to '?'
1394int GetGlyphIndex(Font font, int codepoint)
1395{
1396 int index = 0;
1397 if (!IsFontValid(font)) return index;
1399#define SUPPORT_UNORDERED_CHARSET
1400#if defined(SUPPORT_UNORDERED_CHARSET)
1401 int fallbackIndex = 0; // Get index of fallback glyph '?'
1403 // Look for character index in the unordered charset
1404 for (int i = 0; i < font.glyphCount; i++)
1405 {
1406 if (font.glyphs[i].value == 63) fallbackIndex = i;
1408 if (font.glyphs[i].value == codepoint)
1409 {
1410 index = i;
1411 break;
1412 }
1413 }
1415 if ((index == 0) && (font.glyphs[0].value != codepoint)) index = fallbackIndex;
1416#else
1417 index = codepoint - 32;
1418#endif
1420 return index;
1421}
1423// Get glyph font info data for a codepoint (unicode character)
1424// NOTE: If codepoint is not found in the font it fallbacks to '?'
1425GlyphInfo GetGlyphInfo(Font font, int codepoint)
1426{
1427 GlyphInfo info = { 0 };
1429 info = font.glyphs[GetGlyphIndex(font, codepoint)];
1431 return info;
1432}
1434// Get glyph rectangle in font atlas for a codepoint (unicode character)
1435// NOTE: If codepoint is not found in the font it fallbacks to '?'
1436Rectangle GetGlyphAtlasRec(Font font, int codepoint)
1437{
1438 Rectangle rec = { 0 };
1440 rec = font.recs[GetGlyphIndex(font, codepoint)];
1442 return rec;
1443}
1445//----------------------------------------------------------------------------------
1446// Text strings management functions
1447//----------------------------------------------------------------------------------
1448// Load text as separate lines ('\n')
1449// NOTE: Returned lines end with null terminator '\0'
1450char **LoadTextLines(const char *text, int *count)
1451{
1452 char **lines = NULL;
1453 int lineCount = 0;
1455 if (text != NULL)
1456 {
1457 int textLength = TextLength(text);
1458 lineCount = 1;
1460 // First text scan pass to get required line count
1461 for (int i = 0; i < textLength; i++)
1462 {
1463 if (text[i] == '\n') lineCount++;
1464 }
1466 lines = (char **)RL_CALLOC(lineCount, sizeof(char *));
1467 for (int i = 0, l = 0, lineLen = 0; i <= textLength; i++)
1468 {
1469 if ((text[i] == '\n') || (text[i] == '\0'))
1470 {
1471 lines[l] = (char *)RL_CALLOC(lineLen + 1, 1);
1472 strncpy(lines[l], &text[i - lineLen], lineLen);
1473 lineLen = 0;
1474 l++;
1475 }
1476 else lineLen++;
1477 }
1478 }
1480 *count = lineCount;
1481 return lines;
1482}
1484// Unload text lines
1485void UnloadTextLines(char **lines, int lineCount)
1486{
1487 for (int i = 0; i < lineCount; i++) RL_FREE(lines[i]);
1488 RL_FREE(lines);
1489}
1491// Get text length in bytes, check for \0 character
1492unsigned int TextLength(const char *text)
1493{
1494 unsigned int length = 0;
1496 if (text != NULL)
1497 {
1498 // NOTE: Alternative: use strlen(text)
1500 while (*text++) length++;
1501 }
1503 return length;
1504}
1506// Formatting of text with variables to 'embed'
1507// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times
1508const char *TextFormat(const char *text, ...)
1509{
1510#ifndef MAX_TEXTFORMAT_BUFFERS
1511 #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting
1512#endif
1514 // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations
1515 static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 };
1516 static int index = 0;
1518 char *currentBuffer = buffers[index];
1519 memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using
1521 if (text != NULL)
1522 {
1523 va_list args;
1524 va_start(args, text);
1525 int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args);
1526 va_end(args);
1528 // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occurred
1529 if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH)
1530 {
1531 // Inserting "..." at the end of the string to mark as truncated
1532 char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0"
1533 snprintf(truncBuffer, 4, "...");
1534 }
1536 index += 1; // Move to next buffer for next function call
1537 if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0;
1538 }
1540 return currentBuffer;
1541}
1543// Get integer value from text
1544// NOTE: This function replaces atoi() [stdlib.h]
1545int TextToInteger(const char *text)
1546{
1547 int value = 0;
1548 int sign = 1;
1550 if (text != NULL)
1551 {
1552 if ((text[0] == '+') || (text[0] == '-'))
1553 {
1554 if (text[0] == '-') sign = -1;
1555 text++;
1556 }
1558 for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0');
1559 }
1561 return value*sign;
1562}
1564// Get float value from text
1565// NOTE: This function replaces atof() [stdlib.h]
1566// WARNING: Only '.' character is understood as decimal point
1567float TextToFloat(const char *text)
1568{
1569 float value = 0.0f;
1570 float sign = 1.0f;
1572 if (text != NULL)
1573 {
1574 if ((text[0] == '+') || (text[0] == '-'))
1575 {
1576 if (text[0] == '-') sign = -1.0f;
1577 text++;
1578 }
1580 int i = 0;
1581 for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0');
1583 if (text[i++] == '.')
1584 {
1585 float divisor = 10.0f;
1586 for (; ((text[i] >= '0') && (text[i] <= '9')); i++)
1587 {
1588 value += ((float)(text[i] - '0'))/divisor;
1589 divisor = divisor*10.0f;
1590 }
1591 }
1592 }
1594 return value*sign;
1595}
1597#if defined(SUPPORT_TEXT_MANIPULATION)
1598// Copy one string to another, returns bytes copied
1599// NOTE: Alternative implementation to strcpy(dst, src) from C standard library
1600int TextCopy(char *dst, const char *src)
1601{
1602 int bytes = 0;
1604 if ((src != NULL) && (dst != NULL))
1605 {
1606 while (*src != '\0')
1607 {
1608 *dst = *src;
1609 dst++;
1610 src++;
1612 bytes++;
1613 }
1615 *dst = '\0';
1616 }
1618 return bytes;
1619}
1621// Check if two text string are equal
1622// REQUIRES: strcmp()
1623bool TextIsEqual(const char *text1, const char *text2)
1624{
1625 bool result = false;
1627 if ((text1 != NULL) && (text2 != NULL))
1628 {
1629 if (strcmp(text1, text2) == 0) result = true;
1630 }
1632 return result;
1633}
1635// Get a piece of a text string
1636const char *TextSubtext(const char *text, int position, int length)
1637{
1638 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1639 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1641 if (text != NULL)
1642 {
1643 int textLength = TextLength(text);
1645 if (position >= textLength) return buffer; // First char is already '\0' by memset
1647 int maxLength = textLength - position;
1648 if (length > maxLength) length = maxLength;
1649 if (length >= MAX_TEXT_BUFFER_LENGTH) length = MAX_TEXT_BUFFER_LENGTH - 1;
1651 // NOTE: Alternative: memcpy(buffer, text + position, length)
1653 for (int c = 0; c < length; c++) buffer[c] = text[position + c];
1655 buffer[length] = '\0';
1656 }
1658 return buffer;
1659}
1661// Remove text spaces, concat words
1662const char *TextRemoveSpaces(const char *text)
1663{
1664 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1665 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1667 if (text != NULL)
1668 {
1669 // Avoid copying the ' ' characters
1670 for (int i = 0, j = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++)
1671 {
1672 if (text[i] != ' ') { buffer[j] = text[i]; j++; }
1673 }
1674 }
1676 return buffer;
1677}
1679// Get text between two strings
1680char *GetTextBetween(const char *text, const char *begin, const char *end)
1681{
1682 #define MAX_TEXT_BETWEEN_SIZE 1024
1684 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1685 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1687 int beginIndex = TextFindIndex(text, begin);
1689 if (beginIndex > -1)
1690 {
1691 int beginLen = TextLength(begin);
1692 int endIndex = TextFindIndex(text + beginIndex + beginLen, end);
1694 if (endIndex > -1)
1695 {
1696 endIndex += (beginIndex + beginLen);
1697 int len = (endIndex - beginIndex - beginLen);
1698 if (len < (MAX_TEXT_BETWEEN_SIZE - 1)) strncpy(buffer, text + beginIndex + beginLen, len);
1699 else strncpy(buffer, text + beginIndex + beginLen, MAX_TEXT_BETWEEN_SIZE - 1);
1700 }
1701 }
1703 return buffer;
1704}
1706// Replace text string
1707// REQUIRES: strstr(), strncpy()
1708// TODO: If (replacement == "") remove "search" text
1709// WARNING: Allocated memory must be manually freed
1710char *TextReplace(const char *text, const char *search, const char *replacement)
1711{
1712 char *result = NULL;
1714 if ((text != NULL) && (search != NULL))
1715 {
1716 char *insertPoint = NULL; // Next insert point
1717 char *temp = NULL; // Temp pointer
1718 int textLen = 0; // Text string length
1719 int searchLen = 0; // Search string length of (the string to remove)
1720 int replaceLen = 0; // Replacement length (the string to replace by)
1721 int lastReplacePos = 0; // Distance between next search and end of last replace
1722 int count = 0; // Number of replacements
1724 textLen = TextLength(text);
1725 searchLen = TextLength(search);
1726 if (searchLen == 0) return NULL; // Empty search causes infinite loop during count
1728 replaceLen = TextLength(replacement);
1730 // Count the number of replacements needed
1731 insertPoint = (char *)text;
1732 for (count = 0; (temp = strstr(insertPoint, search)); count++) insertPoint = temp + searchLen;
1734 // Allocate returning string and point temp to it
1735 int tempLen = textLen + (replaceLen - searchLen)*count + 1;
1736 temp = result = (char *)RL_MALLOC(tempLen);
1738 if (!result) return NULL; // Memory could not be allocated
1740 // First time through the loop, all the variable are set correctly from here on,
1741 // - 'temp' points to the end of the result string
1742 // - 'insertPoint' points to the next occurrence of replace in text
1743 // - 'text' points to the remainder of text after "end of replace"
1744 while (count--)
1745 {
1746 insertPoint = (char *)strstr(text, search);
1747 lastReplacePos = (int)(insertPoint - text);
1748 temp = strncpy(temp, text, tempLen - 1) + lastReplacePos;
1749 tempLen -= lastReplacePos;
1750 temp = strncpy(temp, replacement, tempLen - 1) + replaceLen;
1751 tempLen -= replaceLen;
1753 text += lastReplacePos + searchLen; // Move to next "end of replace"
1754 }
1756 // Copy remaind text part after replacement to result (pointed by moving temp)
1757 strncpy(temp, text, tempLen - 1);
1758 }
1760 return result;
1761}
1763// Replace text between two specific strings
1764// REQUIRES: strncpy()
1765// NOTE: If (replacement == NULL) remove "begin"[ ]"end" text
1766// WARNING: Returned string must be freed by user
1767char *TextReplaceBetween(const char *text, const char *begin, const char *end, const char *replacement)
1768{
1769 char *result = NULL;
1771 if ((text != NULL) && (begin != NULL) && (end != NULL))
1772 {
1773 int beginIndex = TextFindIndex(text, begin);
1775 if (beginIndex > -1)
1776 {
1777 int beginLen = TextLength(begin);
1778 int endIndex = TextFindIndex(text + beginIndex + beginLen, end);
1780 if (endIndex > -1)
1781 {
1782 endIndex += (beginIndex + beginLen);
1784 int textLen = TextLength(text);
1785 int replaceLen = (replacement == NULL)? 0 : TextLength(replacement);
1786 int toreplaceLen = endIndex - beginIndex - beginLen;
1787 result = (char *)RL_CALLOC(textLen + replaceLen - toreplaceLen + 1, sizeof(char));
1789 strncpy(result, text, beginIndex + beginLen); // Copy first text part
1790 if (replacement != NULL) strncpy(result + beginIndex + beginLen, replacement, replaceLen); // Copy replacement (if provided)
1791 strncpy(result + beginIndex + beginLen + replaceLen, text + endIndex, textLen - endIndex); // Copy end text part
1792 }
1793 }
1794 }
1796 return result;
1797}
1799// Insert text in a specific position, moves all text forward
1800// WARNING: Allocated memory must be manually freed
1801char *TextInsert(const char *text, const char *insert, int position)
1802{
1803 char *result = NULL;
1805 if ((text != NULL) && (insert != NULL))
1806 {
1807 int textLen = TextLength(text);
1808 int insertLen = TextLength(insert);
1810 result = (char *)RL_MALLOC(textLen + insertLen + 1);
1812 for (int i = 0; i < position; i++) result[i] = text[i];
1813 for (int i = position; i < insertLen + position; i++) result[i] = insert[i];
1814 for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i];
1816 result[textLen + insertLen] = '\0'; // Add EOL
1817 }
1819 return result;
1820}
1822// Join text strings with delimiter
1823// REQUIRES: memset(), memcpy()
1824char *TextJoin(char **textList, int count, const char *delimiter)
1825{
1826 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1827 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1828 char *textPtr = buffer;
1830 int totalLength = 0;
1831 int delimiterLen = TextLength(delimiter);
1833 for (int i = 0; i < count; i++)
1834 {
1835 int textLength = TextLength(textList[i]);
1837 // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH
1838 if ((totalLength + textLength) < MAX_TEXT_BUFFER_LENGTH)
1839 {
1840 memcpy(textPtr, textList[i], textLength);
1841 totalLength += textLength;
1842 textPtr += textLength;
1844 if ((delimiterLen > 0) && (i < (count - 1)))
1845 {
1846 memcpy(textPtr, delimiter, delimiterLen);
1847 totalLength += delimiterLen;
1848 textPtr += delimiterLen;
1849 }
1850 }
1851 }
1853 return buffer;
1854}
1856// Split string into multiple strings
1857// REQUIRES: memset()
1858char **TextSplit(const char *text, char delimiter, int *count)
1859{
1860 // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter)
1861 // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated,
1862 // all used memory is static... it has some limitations:
1863 // 1. Maximum number of possible split strings is set by MAX_TEXTSPLIT_COUNT
1864 // 2. Maximum size of text to split is MAX_TEXT_BUFFER_LENGTH
1866 static char *buffers[MAX_TEXTSPLIT_COUNT] = { NULL }; // Pointers to buffer[] text data
1867 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; // Text data with '\0' separators
1868 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1870 buffers[0] = buffer;
1871 int counter = 0;
1873 if (text != NULL)
1874 {
1875 counter = 1;
1877 // Count how many substrings we have on text and point to every one
1878 for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++)
1879 {
1880 buffer[i] = text[i];
1881 if (buffer[i] == '\0') break;
1882 else if (buffer[i] == delimiter)
1883 {
1884 buffer[i] = '\0'; // Set an end of string at this point
1885 buffers[counter] = buffer + i + 1;
1886 counter++;
1888 if (counter == MAX_TEXTSPLIT_COUNT) break;
1889 }
1890 }
1891 }
1893 *count = counter;
1894 return buffers;
1895}
1897// Append text at specific position and move cursor
1898// WARNING: It's up to the user to make sure appended text does not overflow the buffer!
1899void TextAppend(char *text, const char *append, int *position)
1900{
1901 if ((text != NULL) && (append != NULL))
1902 {
1903 TextCopy(text + *position, append);
1904 *position += TextLength(append);
1905 }
1906}
1908// Find first text occurrence within a string
1909// REQUIRES: strstr()
1910int TextFindIndex(const char *text, const char *search)
1911{
1912 int position = -1;
1914 if (text != NULL)
1915 {
1916 char *ptr = (char *)strstr(text, search);
1918 if (ptr != NULL) position = (int)(ptr - text);
1919 }
1921 return position;
1922}
1924// Get upper case version of provided string
1925// WARNING: Limited functionality, only basic characters set
1926// TODO: Support UTF-8 diacritics to upper-case, check codepoints
1927char *TextToUpper(const char *text)
1928{
1929 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1930 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1932 if (text != NULL)
1933 {
1934 for (int i = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[i] != '\0'); i++)
1935 {
1936 if ((text[i] >= 'a') && (text[i] <= 'z')) buffer[i] = text[i] - 32;
1937 else buffer[i] = text[i];
1938 }
1939 }
1941 return buffer;
1942}
1944// Get lower case version of provided string
1945// WARNING: Limited functionality, only basic characters set
1946char *TextToLower(const char *text)
1947{
1948 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1949 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1951 if (text != NULL)
1952 {
1953 for (int i = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[i] != '\0'); i++)
1954 {
1955 if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32;
1956 else buffer[i] = text[i];
1957 }
1958 }
1960 return buffer;
1961}
1963// Get Pascal case notation version of provided string
1964// WARNING: Limited functionality, only basic characters set
1965char *TextToPascal(const char *text)
1966{
1967 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1968 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1970 if (text != NULL)
1971 {
1972 // Upper case first character
1973 if ((text[0] >= 'a') && (text[0] <= 'z')) buffer[0] = text[0] - 32;
1974 else buffer[0] = text[0];
1976 // Check for next separator to upper case another character
1977 for (int i = 1, j = 1; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++, j++)
1978 {
1979 if (text[j] != '_') buffer[i] = text[j];
1980 else
1981 {
1982 j++;
1983 if ((text[j] >= 'a') && (text[j] <= 'z')) buffer[i] = text[j] - 32;
1984 else if ((text[j] >= '0') && (text[j] <= '9')) buffer[i] = text[j];
1985 }
1986 }
1987 }
1989 return buffer;
1990}
1992// Get snake case notation version of provided string
1993// WARNING: Limited functionality, only basic characters set
1994char *TextToSnake(const char *text)
1995{
1996 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
1997 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
1999 if (text != NULL)
2000 {
2001 // Check for next separator to upper case another character
2002 for (int i = 0, j = 0; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++, j++)
2003 {
2004 if ((text[j] >= 'A') && (text[j] <= 'Z'))
2005 {
2006 if (i >= 1)
2007 {
2008 buffer[i] = '_';
2009 i++;
2010 }
2011 buffer[i] = text[j] + 32;
2012 }
2013 else buffer[i] = text[j];
2014 }
2015 }
2017 return buffer;
2018}
2020// Get Camel case notation version of provided string
2021// WARNING: Limited functionality, only basic characters set
2022char *TextToCamel(const char *text)
2023{
2024 static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
2025 memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
2027 if (text != NULL)
2028 {
2029 // Lower case first character
2030 if ((text[0] >= 'A') && (text[0] <= 'Z')) buffer[0] = text[0] + 32;
2031 else buffer[0] = text[0];
2033 // Check for next separator to upper case another character
2034 for (int i = 1, j = 1; (i < MAX_TEXT_BUFFER_LENGTH - 1) && (text[j] != '\0'); i++, j++)
2035 {
2036 if (text[j] != '_') buffer[i] = text[j];
2037 else
2038 {
2039 j++;
2040 if ((text[j] >= 'a') && (text[j] <= 'z')) buffer[i] = text[j] - 32;
2041 }
2042 }
2043 }
2045 return buffer;
2046}
2048// Encode text codepoint into UTF-8 text
2049// REQUIRES: memcpy()
2050// WARNING: Allocated memory must be manually freed
2051char *LoadUTF8(const int *codepoints, int length)
2052{
2053 char *text = NULL;
2055 if ((codepoints != NULL) && (length > 0))
2056 {
2057 // We allocate enough memory to fit all possible codepoints
2058 // NOTE: 5 bytes for every codepoint should be enough
2059 text = (char *)RL_CALLOC(length*5, 1);
2060 const char *utf8 = NULL;
2061 int size = 0;
2063 for (int i = 0, bytes = 0; i < length; i++)
2064 {
2065 utf8 = CodepointToUTF8(codepoints[i], &bytes);
2066 memcpy(text + size, utf8, bytes);
2067 size += bytes;
2068 }
2070 // Create second buffer and copy data manually to it
2071 char *temp = (char *)RL_CALLOC(size + 1, 1);
2072 memcpy(temp, text, size);
2073 RL_FREE(text);
2074 text = temp;
2075 }
2077 return text;
2078}
2080// Unload UTF-8 text encoded from codepoints array
2081void UnloadUTF8(char *text)
2082{
2083 RL_FREE(text);
2084}
2086// Load all codepoints from a UTF-8 text string, codepoints count returned by parameter
2087int *LoadCodepoints(const char *text, int *count)
2088{
2089 int *codepoints = NULL;
2090 int codepointCount = 0;
2092 if (text != NULL)
2093 {
2094 int textLength = TextLength(text);
2096 // Allocate a big enough buffer to store as many codepoints as text bytes
2097 codepoints = (int *)RL_CALLOC(textLength, sizeof(int));
2099 int codepointSize = 0;
2100 for (int i = 0; i < textLength; codepointCount++)
2101 {
2102 codepoints[codepointCount] = GetCodepointNext(text + i, &codepointSize);
2103 i += codepointSize;
2104 }
2106 // Create second buffer and copy data manually to it
2107 int *temp = (int *)RL_CALLOC(codepointCount, sizeof(int));
2108 for (int i = 0; i < codepointCount; i++) temp[i] = codepoints[i];
2109 RL_FREE(codepoints);
2110 codepoints = temp;
2111 }
2113 *count = codepointCount;
2114 return codepoints;
2115}
2117// Unload codepoints data from memory
2118void UnloadCodepoints(int *codepoints)
2119{
2120 RL_FREE(codepoints);
2121}
2123// Get total number of characters(codepoints) in a UTF-8 encoded text, until '\0' is found
2124// NOTE: If an invalid UTF-8 sequence is encountered a '?'(0x3f) codepoint is counted instead
2125int GetCodepointCount(const char *text)
2126{
2127 unsigned int length = 0;
2128 const char *ptr = text;
2130 if (ptr != NULL)
2131 {
2132 while (*ptr != '\0')
2133 {
2134 int next = 0;
2135 GetCodepointNext(ptr, &next);
2136 ptr += next;
2137 length++;
2138 }
2139 }
2141 return length;
2142}
2144// Encode codepoint into utf8 text (char array length returned as parameter)
2145// NOTE: It uses a static array to store UTF-8 bytes
2146const char *CodepointToUTF8(int codepoint, int *utf8Size)
2147{
2148 static char utf8[6] = { 0 };
2149 memset(utf8, 0, 6); // Clear static array
2150 int size = 0; // Byte size of codepoint
2152 if (codepoint <= 0x7f)
2153 {
2154 utf8[0] = (char)codepoint;
2155 size = 1;
2156 }
2157 else if (codepoint <= 0x7ff)
2158 {
2159 utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0);
2160 utf8[1] = (char)((codepoint & 0x3f) | 0x80);
2161 size = 2;
2162 }
2163 else if (codepoint <= 0xffff)
2164 {
2165 utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0);
2166 utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80);
2167 utf8[2] = (char)((codepoint & 0x3f) | 0x80);
2168 size = 3;
2169 }
2170 else if (codepoint <= 0x10ffff)
2171 {
2172 utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0);
2173 utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80);
2174 utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80);
2175 utf8[3] = (char)((codepoint & 0x3f) | 0x80);
2176 size = 4;
2177 }
2179 *utf8Size = size;
2181 return utf8;
2182}
2183#endif // SUPPORT_TEXT_MANIPULATION
2185// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found
2186// When an invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned
2187// Total number of bytes processed are returned as a parameter
2188// NOTE: The standard says U+FFFD should be returned in case of errors
2189// but that character is not supported by the default font in raylib
2190int GetCodepoint(const char *text, int *codepointSize)
2191{
2192/*
2193 UTF-8 specs from https://www.ietf.org/rfc/rfc3629.txt
2195 Char. number range | UTF-8 octet sequence
2196 (hexadecimal) | (binary)
2197 --------------------+---------------------------------------------
2198 0000 0000-0000 007F | 0xxxxxxx
2199 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
2200 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
2201 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
2202*/
2205 int codepoint = 0x3f; // Codepoint (defaults to '?')
2206 *codepointSize = 1;
2207 if (text == NULL) return codepoint;
2209 // NOTE: on decode errors we return as soon as possible
2210 int octet = (unsigned char)(text[0]); // The first UTF8 octet
2212 if (octet <= 0x7f)
2213 {
2214 // Only one octet (ASCII range x00-7F)
2215 codepoint = text[0];
2216 }
2217 else if ((octet & 0xe0) == 0xc0)
2218 {
2219 // Two octets
2221 // [0]xC2-DF [1]UTF8-tail(x80-BF)
2222 unsigned char octet1 = text[1];
2224 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence
2226 if ((octet >= 0xc2) && (octet <= 0xdf))
2227 {
2228 codepoint = ((octet & 0x1f) << 6) | (octet1 & 0x3f);
2229 *codepointSize = 2;
2230 }
2231 }
2232 else if ((octet & 0xf0) == 0xe0)
2233 {
2234 // Three octets
2235 unsigned char octet1 = text[1];
2236 unsigned char octet2 = '\0';
2238 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence
2240 octet2 = text[2];
2242 if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *codepointSize = 3; return codepoint; } // Unexpected sequence
2244 // [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF)
2245 // [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF)
2246 // [0]xED [1]x80-9F [2]UTF8-tail(x80-BF)
2247 // [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF)
2249 if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) ||
2250 ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *codepointSize = 2; return codepoint; }
2252 if ((octet >= 0xe0) && (octet <= 0xef))
2253 {
2254 codepoint = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);
2255 *codepointSize = 3;
2256 }
2257 }
2258 else if ((octet & 0xf8) == 0xf0)
2259 {
2260 // Four octets
2261 if (octet > 0xf4) return codepoint;
2263 unsigned char octet1 = text[1];
2264 unsigned char octet2 = '\0';
2265 unsigned char octet3 = '\0';
2267 if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *codepointSize = 2; return codepoint; } // Unexpected sequence
2269 octet2 = text[2];
2271 if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *codepointSize = 3; return codepoint; } // Unexpected sequence
2273 octet3 = text[3];
2275 if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *codepointSize = 4; return codepoint; } // Unexpected sequence
2277 // [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail
2278 // [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail
2279 // [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail
2281 if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||
2282 ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *codepointSize = 2; return codepoint; } // Unexpected sequence
2284 if (octet >= 0xf0)
2285 {
2286 codepoint = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f);
2287 *codepointSize = 4;
2288 }
2289 }
2291 if (codepoint > 0x10ffff) codepoint = 0x3f; // Codepoints after U+10ffff are invalid
2293 return codepoint;
2294}
2296// Get next codepoint in a byte sequence and bytes processed
2297int GetCodepointNext(const char *text, int *codepointSize)
2298{
2299 const char *ptr = text;
2300 int codepoint = 0x3f; // Codepoint (defaults to '?')
2301 *codepointSize = 1;
2302 if (text == NULL) return codepoint;
2304 // Get current codepoint and bytes processed
2305 if (0xf0 == (0xf8 & ptr[0]))
2306 {
2307 // 4 byte UTF-8 codepoint
2308 if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks
2309 codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]);
2310 *codepointSize = 4;
2311 }
2312 else if (0xe0 == (0xf0 & ptr[0]))
2313 {
2314 // 3 byte UTF-8 codepoint */
2315 if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } // 10xxxxxx checks
2316 codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]);
2317 *codepointSize = 3;
2318 }
2319 else if (0xc0 == (0xe0 & ptr[0]))
2320 {
2321 // 2 byte UTF-8 codepoint
2322 if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } // 10xxxxxx checks
2323 codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]);
2324 *codepointSize = 2;
2325 }
2326 else if (0x00 == (0x80 & ptr[0]))
2327 {
2328 // 1 byte UTF-8 codepoint
2329 codepoint = ptr[0];
2330 *codepointSize = 1;
2331 }
2333 return codepoint;
2334}
2336// Get previous codepoint in a byte sequence and bytes processed
2337int GetCodepointPrevious(const char *text, int *codepointSize)
2338{
2339 const char *ptr = text;
2340 int codepoint = 0x3f; // Codepoint (defaults to '?')
2341 *codepointSize = 1;
2342 if (text == NULL) return codepoint;
2344 // Move to previous codepoint
2345 do ptr--;
2346 while (((0x80 & ptr[0]) != 0) && ((0xc0 & ptr[0]) == 0x80));
2348 int cpSize = 0;
2349 codepoint = GetCodepointNext(ptr, &cpSize);
2350 if (codepoint != 0) *codepointSize = cpSize;
2352 return codepoint;
2353}
2355//----------------------------------------------------------------------------------
2356// Module Internal Functions Definition
2357//----------------------------------------------------------------------------------
2358#if defined(SUPPORT_FILEFORMAT_FNT) || defined(SUPPORT_FILEFORMAT_BDF)
2359// Read a line from memory
2360// REQUIRES: memcpy()
2361// NOTE: Returns the number of bytes read
2362static int GetLine(const char *origin, char *buffer, int maxLength)
2363{
2364 int count = 0;
2365 for (; count < maxLength - 1; count++) if (origin[count] == '\n') break;
2366 memcpy(buffer, origin, count);
2367 buffer[count] = '\0';
2368 return count;
2369}
2370#endif
2372#if defined(SUPPORT_FILEFORMAT_FNT)
2373// Load a BMFont file (AngelCode font file)
2374// REQUIRES: strstr(), sscanf(), strrchr(), memcpy()
2375static Font LoadBMFont(const char *fileName)
2376{
2377 #define MAX_BUFFER_SIZE 256
2378 #define MAX_FONT_IMAGE_PAGES 8
2380 Font font = { 0 };
2382 char buffer[MAX_BUFFER_SIZE] = { 0 };
2383 char *searchPoint = NULL;
2385 int fontSize = 0;
2386 int glyphCount = 0;
2388 int imWidth = 0;
2389 int imHeight = 0;
2390 int pageCount = 1;
2391 char imFileName[MAX_FONT_IMAGE_PAGES][129] = { 0 };
2393 int base = 0; // Useless data
2394 int readBytes = 0; // Data bytes read
2395 int readVars = 0; // Variables filled by sscanf()
2397 char *fileText = LoadFileText(fileName);
2399 if (fileText == NULL) return font;
2401 char *fileTextPtr = fileText;
2403 // NOTE: We skip first line, it contains no useful information
2404 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2405 fileTextPtr += (readBytes + 1);
2407 // Read line data
2408 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2409 searchPoint = strstr(buffer, "lineHeight");
2410 readVars = sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i pages=%i", &fontSize, &base, &imWidth, &imHeight, &pageCount);
2411 fileTextPtr += (readBytes + 1);
2413 if (readVars < 4) { UnloadFileText(fileText); return font; } // Some data not available, file malformed
2415 if (pageCount > MAX_FONT_IMAGE_PAGES)
2416 {
2417 TRACELOG(LOG_WARNING, "FONT: [%s] Font defines more pages than supported: %i/%i", fileName, pageCount, MAX_FONT_IMAGE_PAGES);
2418 pageCount = MAX_FONT_IMAGE_PAGES;
2419 }
2421 for (int i = 0; i < pageCount; i++)
2422 {
2423 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2424 searchPoint = strstr(buffer, "file");
2425 readVars = sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName[i]);
2426 fileTextPtr += (readBytes + 1);
2428 if (readVars < 1) { UnloadFileText(fileText); return font; } // No fileName read
2429 }
2431 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2432 searchPoint = strstr(buffer, "count");
2433 readVars = sscanf(searchPoint, "count=%i", &glyphCount);
2434 fileTextPtr += (readBytes + 1);
2436 if (readVars < 1) { UnloadFileText(fileText); return font; } // No glyphCount read
2438 // Load all required images for further compose
2439 Image *imFonts = (Image *)RL_CALLOC(pageCount, sizeof(Image)); // Font atlases, multiple images
2441 for (int i = 0; i < pageCount; i++)
2442 {
2443 imFonts[i] = LoadImage(TextFormat("%s/%s", GetDirectoryPath(fileName), imFileName[i]));
2445 if (imFonts[i].format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)
2446 {
2447 // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel
2448 Image imFontAlpha = {
2449 .data = RL_CALLOC(imFonts[i].width*imFonts[i].height, 2),
2450 .width = imFonts[i].width,
2451 .height = imFonts[i].height,
2452 .mipmaps = 1,
2453 .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA
2454 };
2456 for (int p = 0, pi = 0; p < (imFonts[i].width*imFonts[i].height*2); p += 2, pi++)
2457 {
2458 ((unsigned char *)(imFontAlpha.data))[p] = 0xff;
2459 ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFonts[i].data)[pi];
2460 }
2462 UnloadImage(imFonts[i]);
2463 imFonts[i] = imFontAlpha;
2464 }
2465 }
2467 Image fullFont = imFonts[0];
2468 for (int i = 1; i < pageCount; i++) UnloadImage(imFonts[i]);
2470 // If multiple atlas, then merge atlas
2471 // NOTE: WARNING: This process could be really slow!
2472 if (pageCount > 1)
2473 {
2474 // Resize font atlas to draw additional images
2475 ImageResizeCanvas(&fullFont, imWidth, imHeight*pageCount, 0, 0, BLACK);
2477 for (int i = 1; i < pageCount; i++)
2478 {
2479 Rectangle srcRec = { 0.0f, 0.0f, (float)imWidth, (float)imHeight };
2480 Rectangle destRec = { 0.0f, (float)imHeight*(float)i, (float)imWidth, (float)imHeight };
2481 ImageDraw(&fullFont, imFonts[i], srcRec, destRec, WHITE);
2482 }
2483 }
2485 RL_FREE(imFonts);
2487 font.texture = LoadTextureFromImage(fullFont);
2489 // Fill font characters info data
2490 font.baseSize = fontSize;
2491 font.glyphCount = glyphCount;
2492 font.glyphPadding = 0;
2493 font.glyphs = (GlyphInfo *)RL_MALLOC(glyphCount*sizeof(GlyphInfo));
2494 font.recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle));
2496 int charId = 0;
2497 int charX = 0;
2498 int charY = 0;
2499 int charWidth = 0;
2500 int charHeight = 0;
2501 int charOffsetX = 0;
2502 int charOffsetY = 0;
2503 int charAdvanceX = 0;
2504 int pageID = 0;
2506 for (int i = 0; i < glyphCount; i++)
2507 {
2508 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2509 readVars = sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i",
2510 &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID);
2511 fileTextPtr += (readBytes + 1);
2513 if (readVars == 9) // Make sure all char data has been properly read
2514 {
2515 // Get character rectangle in the font atlas texture
2516 font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight*pageID, (float)charWidth, (float)charHeight };
2518 // Save data properly in sprite font
2519 font.glyphs[i].value = charId;
2520 font.glyphs[i].offsetX = charOffsetX;
2521 font.glyphs[i].offsetY = charOffsetY;
2522 font.glyphs[i].advanceX = charAdvanceX;
2524 // Fill character image data from full font data
2525 font.glyphs[i].image = ImageFromImage(fullFont, font.recs[i]);
2526 }
2527 else
2528 {
2529 font.glyphs[i].image = GenImageColor((int)font.recs[i].width, (int)font.recs[i].height, BLACK);
2530 TRACELOG(LOG_WARNING, "FONT: [%s] Some characters data not correctly provided", fileName);
2531 }
2532 }
2534 UnloadImage(fullFont);
2535 UnloadFileText(fileText);
2537 if (font.texture.id == 0)
2538 {
2539 UnloadFont(font);
2540 font = GetFontDefault();
2541 TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load texture, reverted to default font", fileName);
2542 }
2543 else TRACELOG(LOG_INFO, "FONT: [%s] Font loaded successfully (%i glyphs)", fileName, font.glyphCount);
2545 return font;
2546}
2547#endif
2549#if defined(SUPPORT_FILEFORMAT_BDF)
2550// Convert hexadecimal to decimal (single digit)
2551static unsigned char HexToInt(char hex)
2552{
2553 if ((hex >= '0') && (hex <= '9')) return hex - '0';
2554 else if ((hex >= 'a') && (hex <= 'f')) return hex - 'a' + 10;
2555 else if ((hex >= 'A') && (hex <= 'F')) return hex - 'A' + 10;
2556 else return 0;
2557}
2559// Load font data for further use
2560// NOTE: Requires BDF font memory data
2561static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, const int *codepoints, int codepointCount, int *outFontSize)
2562{
2563 #define MAX_BUFFER_SIZE 256
2565 char buffer[MAX_BUFFER_SIZE] = { 0 };
2567 GlyphInfo *glyphs = NULL;
2568 bool internalCodepoints = false;
2570 int totalReadBytes = 0; // Data bytes read (total)
2571 int readBytes = 0; // Data bytes read (line)
2572 int readVars = 0; // Variables filled by sscanf()
2574 const char *fileText = (const char *)fileData;
2575 const char *fileTextPtr = fileText;
2577 bool fontMalformed = false; // Is the font malformed
2578 bool fontStarted = false; // Has font started (STARTFONT)
2579 int fontBBw = 0; // Font base character bounding box width
2580 int fontBBh = 0; // Font base character bounding box height
2581 int fontBBxoff0 = 0; // Font base character bounding box X0 offset
2582 int fontBByoff0 = 0; // Font base character bounding box Y0 offset
2583 int fontAscent = 0; // Font ascent
2585 bool charStarted = false; // Has character started (STARTCHAR)
2586 bool charBitmapStarted = false; // Has bitmap data started (BITMAP)
2587 int charBitmapNextRow = 0; // Y position for the next row of bitmap data
2588 int charEncoding = -1; // The unicode value of the character (-1 if not set)
2589 int charBBw = 0; // Character bounding box width
2590 int charBBh = 0; // Character bounding box height
2591 int charBBxoff0 = 0; // Character bounding box X0 offset
2592 int charBByoff0 = 0; // Character bounding box Y0 offset
2593 int charDWidthX = 0; // Character advance X
2594 int charDWidthY = 0; // Character advance Y (unused)
2596 int *requiredCodepoints = (int *)RL_MALLOC(codepointCount*sizeof(int));
2598 if (fileData == NULL) return glyphs;
2600 // In case no chars count provided, default to 95
2601 codepointCount = (codepointCount > 0)? codepointCount : 95;
2603 if (codepoints == NULL)
2604 {
2605 // Fill internal codepoints array in case not provided externally
2606 // NOTE: By default we fill glyphCount consecutively, starting at 32 (Space)
2607 for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = i + 32;
2608 internalCodepoints = true;
2609 }
2610 else
2611 {
2612 for (int i = 0; i < codepointCount; i++) requiredCodepoints[i] = codepoints[i];
2613 }
2615 glyphs = (GlyphInfo *)RL_CALLOC(codepointCount, sizeof(GlyphInfo));
2617 while (totalReadBytes <= dataSize)
2618 {
2619 readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE);
2620 totalReadBytes += (readBytes + 1);
2621 fileTextPtr += (readBytes + 1);
2623 // Line: COMMENT
2624 if (strstr(buffer, "COMMENT") != NULL) continue; // Ignore line
2626 if (charStarted)
2627 {
2628 // Line: ENDCHAR
2629 if (strstr(buffer, "ENDCHAR") != NULL)
2630 {
2631 charStarted = false;
2632 continue;
2633 }
2635 if (charBitmapStarted)
2636 {
2637 if (glyphs != NULL)
2638 {
2639 int pixelY = charBitmapNextRow++;
2641 if (pixelY >= glyphs->image.height) break;
2643 for (int x = 0; x < readBytes; x++)
2644 {
2645 unsigned char byte = HexToInt(buffer[x]);
2647 for (int bitX = 0; bitX < 4; bitX++)
2648 {
2649 int pixelX = ((x*4) + bitX);
2651 if (pixelX >= glyphs->image.width) break;
2653 if ((byte & (8 >> bitX)) > 0) ((unsigned char *)glyphs->image.data)[(pixelY*glyphs->image.width) + pixelX] = 255;
2654 }
2655 }
2656 }
2657 continue;
2658 }
2660 // Line: ENCODING
2661 if (strstr(buffer, "ENCODING") != NULL)
2662 {
2663 readVars = sscanf(buffer, "ENCODING %i", &charEncoding);
2664 continue;
2665 }
2667 // Line: BBX
2668 if (strstr(buffer, "BBX") != NULL)
2669 {
2670 readVars = sscanf(buffer, "BBX %i %i %i %i", &charBBw, &charBBh, &charBBxoff0, &charBByoff0);
2671 continue;
2672 }
2674 // Line: DWIDTH
2675 if (strstr(buffer, "DWIDTH") != NULL)
2676 {
2677 readVars = sscanf(buffer, "DWIDTH %i %i", &charDWidthX, &charDWidthY);
2678 continue;
2679 }
2681 // Line: BITMAP
2682 if (strstr(buffer, "BITMAP") != NULL)
2683 {
2684 // Search for glyph index in codepoints
2685 glyphs = NULL;
2687 for (int index = 0; index < codepointCount; index++)
2688 {
2689 if (requiredCodepoints[index] == charEncoding)
2690 {
2691 glyphs = &glyphs[index];
2692 break;
2693 }
2694 }
2696 // Init glyph info
2697 if (glyphs != NULL)
2698 {
2699 glyphs->value = charEncoding;
2700 glyphs->offsetX = charBBxoff0 + fontBByoff0;
2701 glyphs->offsetY = fontBBh - (charBBh + charBByoff0 + fontBByoff0 + fontAscent);
2702 glyphs->advanceX = charDWidthX;
2704 glyphs->image.data = RL_CALLOC(charBBw*charBBh, 1);
2705 glyphs->image.width = charBBw;
2706 glyphs->image.height = charBBh;
2707 glyphs->image.mipmaps = 1;
2708 glyphs->image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
2709 }
2711 charBitmapStarted = true;
2712 charBitmapNextRow = 0;
2714 continue;
2715 }
2716 }
2717 else if (fontStarted)
2718 {
2719 // Line: ENDFONT
2720 if (strstr(buffer, "ENDFONT") != NULL)
2721 {
2722 fontStarted = false;
2723 break;
2724 }
2726 // Line: SIZE
2727 if (strstr(buffer, "SIZE") != NULL)
2728 {
2729 if (outFontSize != NULL) readVars = sscanf(buffer, "SIZE %i", outFontSize);
2730 continue;
2731 }
2733 // PIXEL_SIZE
2734 if (strstr(buffer, "PIXEL_SIZE") != NULL)
2735 {
2736 if (outFontSize != NULL) readVars = sscanf(buffer, "PIXEL_SIZE %i", outFontSize);
2737 continue;
2738 }
2740 // FONTBOUNDINGBOX
2741 if (strstr(buffer, "FONTBOUNDINGBOX") != NULL)
2742 {
2743 readVars = sscanf(buffer, "FONTBOUNDINGBOX %i %i %i %i", &fontBBw, &fontBBh, &fontBBxoff0, &fontBByoff0);
2744 continue;
2745 }
2747 // FONT_ASCENT
2748 if (strstr(buffer, "FONT_ASCENT") != NULL)
2749 {
2750 readVars = sscanf(buffer, "FONT_ASCENT %i", &fontAscent);
2751 continue;
2752 }
2754 // STARTCHAR
2755 if (strstr(buffer, "STARTCHAR") != NULL)
2756 {
2757 charStarted = true;
2758 charEncoding = -1;
2759 glyphs = NULL;
2760 charBBw = 0;
2761 charBBh = 0;
2762 charBBxoff0 = 0;
2763 charBByoff0 = 0;
2764 charDWidthX = 0;
2765 charDWidthY = 0;
2766 glyphs = NULL;
2767 charBitmapStarted = false;
2768 charBitmapNextRow = 0;
2769 continue;
2770 }
2771 }
2772 else
2773 {
2774 // STARTFONT
2775 if (strstr(buffer, "STARTFONT") != NULL)
2776 {
2777 if (fontStarted)
2778 {
2779 fontMalformed = true;
2780 break;
2781 }
2782 else
2783 {
2784 fontStarted = true;
2785 continue;
2786 }
2787 }
2788 }
2789 }
2791 RL_FREE(requiredCodepoints);
2793 if (fontMalformed)
2794 {
2795 RL_FREE(glyphs);
2796 glyphs = NULL;
2797 }
2799 return glyphs;
2800}
2801#endif // SUPPORT_FILEFORMAT_BDF
2803#endif // SUPPORT_MODULE_RTEXT
index : raylib-jai
---