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