diff --git a/examples/text/text_font_sdf.c b/examples/text/text_font_sdf.c index 41ce3b2a2..86545e809 100644 --- a/examples/text/text_font_sdf.c +++ b/examples/text/text_font_sdf.c @@ -17,6 +17,8 @@ #define GLSL_VERSION 100 #endif +#include + int main(void) { // Initialization @@ -37,7 +39,7 @@ int main(void) // Parameters > font size: 16, no chars array provided (0), chars count: 95 (autogenerate chars array) fontDefault.chars = LoadFontData("resources/AnonymousPro-Bold.ttf", 16, 0, 95, FONT_DEFAULT); // Parameters > chars count: 95, font size: 16, chars padding in image: 4 px, pack method: 0 (default) - Image atlas = GenImageFontAtlas(fontDefault.chars, 95, 16, 4, 0); + Image atlas = GenImageFontAtlas(fontDefault.chars, &fontDefault.recs, 95, 16, 4, 0); fontDefault.texture = LoadTextureFromImage(atlas); UnloadImage(atlas); @@ -48,7 +50,7 @@ int main(void) // Parameters > font size: 16, no chars array provided (0), chars count: 0 (defaults to 95) fontSDF.chars = LoadFontData("resources/AnonymousPro-Bold.ttf", 16, 0, 0, FONT_SDF); // Parameters > chars count: 95, font size: 16, chars padding in image: 0 px, pack method: 1 (Skyline algorythm) - atlas = GenImageFontAtlas(fontSDF.chars, 95, 16, 0, 1); + atlas = GenImageFontAtlas(fontSDF.chars, &fontSDF.recs, 95, 16, 0, 1); fontSDF.texture = LoadTextureFromImage(atlas); UnloadImage(atlas); diff --git a/examples/textures/textures_image_drawing.c b/examples/textures/textures_image_drawing.c index f5c3c85db..02caf1f93 100644 --- a/examples/textures/textures_image_drawing.c +++ b/examples/textures/textures_image_drawing.c @@ -32,7 +32,7 @@ int main(void) Image parrots = LoadImage("resources/parrots.png"); // Load image in CPU memory (RAM) // Draw one image over the other with a scaling of 1.5f - ImageDraw(&parrots, cat, (Rectangle){ 0, 0, cat.width, cat.height }, (Rectangle){ 30, 40, cat.width*1.5f, cat.height*1.5f }); + ImageDraw(&parrots, cat, (Rectangle){ 0, 0, cat.width, cat.height }, (Rectangle){ 30, 40, cat.width*1.5f, cat.height*1.5f }, WHITE); ImageCrop(&parrots, (Rectangle){ 0, 50, parrots.width, parrots.height - 100 }); // Crop resulting image UnloadImage(cat); // Unload image from RAM diff --git a/games/transmission/screens/screen_gameplay.c b/games/transmission/screens/screen_gameplay.c index 3dfce7145..0541c863e 100644 --- a/games/transmission/screens/screen_gameplay.c +++ b/games/transmission/screens/screen_gameplay.c @@ -152,7 +152,7 @@ void InitGameplayScreen(void) { ImageDraw(&imWords, imWordsBase, (Rectangle){ 0, 0, imWordsBase.width, imWordsBase.height }, - (Rectangle){ 0, imWordsBase.height*i, imWordsBase.width, imWordsBase.height }); + (Rectangle){ 0, imWordsBase.height*i, imWordsBase.width, imWordsBase.height }, WHITE); ImageDrawTextEx(&imWords,(Vector2){ imWordsBase.width/2 - MeasureTextEx(fontMessage, codingWords[i], fontMessage.baseSize, 0).x/2, imWordsBase.height*i }, fontMessage, codingWords[i], diff --git a/src/raylib.h b/src/raylib.h index f9f5882ab..fc66e1499 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -263,18 +263,18 @@ typedef struct NPatchInfo { // Font character info typedef struct CharInfo { int value; // Character value (Unicode) - Rectangle rec; // Character rectangle in sprite font int offsetX; // Character offset X when drawing int offsetY; // Character offset Y when drawing int advanceX; // Character advance position X - unsigned char *data; // Character pixel data (grayscale) + Image image; // Character image data } CharInfo; // Font type, includes texture and charSet array data typedef struct Font { - Texture2D texture; // Font texture int baseSize; // Base size (default chars height) int charsCount; // Number of characters + Texture2D texture; // Characters texture atlas + Rectangle *recs; // Characters rectangles in texture CharInfo *chars; // Characters info data } Font; @@ -1100,6 +1100,7 @@ RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Image manipulation functions RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece RLAPI void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image @@ -1115,7 +1116,7 @@ RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); RLAPI Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount); // Extract color palette from image to maximum size (memory should be freed) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec); // Draw a source image within a destination image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawRectangle(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image RLAPI void ImageDrawText(Image *dst, Vector2 position, const char *text, int fontSize, Color color); // Draw text (default font) within an image (destination) @@ -1165,7 +1166,7 @@ RLAPI Font LoadFont(const char *fileName); RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount); // Load font from file with extended parameters RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions @@ -1180,8 +1181,8 @@ RLAPI void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontS RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font RLAPI int GetGlyphIndex(Font font, int character); // Get index position for a unicode character on font -RLAPI int GetNextCodepoint(const char *text, int *count); // Returns next codepoint in a UTF8 encoded string - // NOTE: 0x3f(`?`) is returned on failure, `count` will hold the total number of bytes processed +RLAPI int GetNextCodepoint(const char *text, int *bytesProcessed); // Returns next codepoint in a UTF8 encoded string + // NOTE: 0x3f('?') is returned on failure // Text strings management functions // NOTE: Some strings allocate memory internally for returned strings, just be careful! diff --git a/src/shapes.c b/src/shapes.c index 190bd4e1b..d1956b26a 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -1522,7 +1522,7 @@ static Texture2D GetShapesTexture(void) { #if defined(SUPPORT_FONT_TEXTURE) texShapes = GetFontDefault().texture; // Use font texture white character - Rectangle rec = GetFontDefault().chars[95].rec; + Rectangle rec = GetFontDefault().recs[95]; // NOTE: We setup a 1px padding on char rectangle to avoid texture bleeding on MSAA filtering recTexShapes = (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }; #else diff --git a/src/text.c b/src/text.c index eb956d1bc..1c775c8db 100644 --- a/src/text.c +++ b/src/text.c @@ -193,13 +193,12 @@ extern void LoadFontDefault(void) if (counter > 512) counter = 0; // Security check... } - Image image = LoadImageEx(imagePixels, imWidth, imHeight); - ImageFormat(&image, UNCOMPRESSED_GRAY_ALPHA); + Image imFont = LoadImageEx(imagePixels, imWidth, imHeight); + ImageFormat(&imFont, UNCOMPRESSED_GRAY_ALPHA); RL_FREE(imagePixels); - defaultFont.texture = LoadTextureFromImage(image); - UnloadImage(image); + defaultFont.texture = LoadTextureFromImage(imFont); // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount //------------------------------------------------------------------------------ @@ -207,6 +206,7 @@ extern void LoadFontDefault(void) // Allocate space for our characters info data // NOTE: This memory should be freed at end! --> CloseWindow() defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo)); + defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle)); int currentLine = 0; int currentPosX = charsDivisor; @@ -216,12 +216,12 @@ extern void LoadFontDefault(void) { defaultFont.chars[i].value = 32 + i; // First char is 32 - defaultFont.chars[i].rec.x = (float)currentPosX; - defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); - defaultFont.chars[i].rec.width = (float)charsWidth[i]; - defaultFont.chars[i].rec.height = (float)charsHeight; + defaultFont.recs[i].x = (float)currentPosX; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].width = (float)charsWidth[i]; + defaultFont.recs[i].height = (float)charsHeight; - testPosX += (int)(defaultFont.chars[i].rec.width + (float)charsDivisor); + testPosX += (int)(defaultFont.recs[i].width + (float)charsDivisor); if (testPosX >= defaultFont.texture.width) { @@ -229,8 +229,8 @@ extern void LoadFontDefault(void) currentPosX = 2*charsDivisor + charsWidth[i]; testPosX = currentPosX; - defaultFont.chars[i].rec.x = (float)charsDivisor; - defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); + defaultFont.recs[i].x = (float)charsDivisor; + defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); } else currentPosX = testPosX; @@ -238,9 +238,14 @@ extern void LoadFontDefault(void) defaultFont.chars[i].offsetX = 0; defaultFont.chars[i].offsetY = 0; defaultFont.chars[i].advanceX = 0; + + // Fill character image data from fontClear data + defaultFont.chars[i].image = ImageFromImage(imFont, defaultFont.recs[i]); } - defaultFont.baseSize = (int)defaultFont.chars[0].rec.height; + UnloadImage(imFont); + + defaultFont.baseSize = (int)defaultFont.recs[0].height; TraceLog(LOG_INFO, "[TEX ID %i] Default font loaded successfully", defaultFont.texture.id); } @@ -248,8 +253,10 @@ extern void LoadFontDefault(void) // Unload raylib default font extern void UnloadFontDefault(void) { + for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image); UnloadTexture(defaultFont.texture); RL_FREE(defaultFont.chars); + RL_FREE(defaultFont.recs); } #endif // SUPPORT_DEFAULT_FONT @@ -312,12 +319,21 @@ Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCou #if defined(SUPPORT_FILEFORMAT_TTF) if (font.chars != NULL) { - Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0); + Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, 2, 0); font.texture = LoadTextureFromImage(atlas); + + // Update chars[i].image to use alpha, required to be used on ImageDrawText() + for (int i = 0; i < font.charsCount; i++) + { + UnloadImage(font.chars[i].image); + font.chars[i].image = ImageFromImage(atlas, font.recs[i]); + } + UnloadImage(atlas); } else font = GetFontDefault(); #else + UnloadFont(font); font = GetFontDefault(); #endif @@ -415,25 +431,30 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) spriteFont.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture spriteFont.charsCount = index; - UnloadImage(fontClear); // Unload processed image once converted to texture - // We got tempCharValues and tempCharsRecs populated with chars data // Now we move temp data to sized charValues and charRecs arrays spriteFont.chars = (CharInfo *)RL_MALLOC(spriteFont.charsCount*sizeof(CharInfo)); + spriteFont.recs = (Rectangle *)RL_MALLOC(spriteFont.charsCount*sizeof(Rectangle)); for (int i = 0; i < spriteFont.charsCount; i++) { spriteFont.chars[i].value = tempCharValues[i]; - spriteFont.chars[i].rec = tempCharRecs[i]; + + // Get character rectangle in the font atlas texture + spriteFont.recs[i] = tempCharRecs[i]; // NOTE: On image based fonts (XNA style), character offsets and xAdvance are not required (set to 0) spriteFont.chars[i].offsetX = 0; spriteFont.chars[i].offsetY = 0; spriteFont.chars[i].advanceX = 0; - spriteFont.chars[i].data = NULL; + + // Fill character image data from fontClear data + spriteFont.chars[i].image = ImageFromImage(fontClear, tempCharRecs[i]); } - spriteFont.baseSize = (int)spriteFont.chars[0].rec.height; + UnloadImage(fontClear); // Unload processed image once converted to texture + + spriteFont.baseSize = (int)spriteFont.recs[0].height; TraceLog(LOG_INFO, "Image file loaded correctly as Font"); @@ -510,9 +531,9 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide - if (type != FONT_SDF) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); - else if (ch != 32) chars[i].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); - else chars[i].data = NULL; + if (type != FONT_SDF) chars[i].image.data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY); + 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); + else chars[i].image.data = NULL; if (type == FONT_BITMAP) { @@ -520,13 +541,17 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c // NOTE: For optimum results, bitmap font should be generated at base pixel size for (int p = 0; p < chw*chh; p++) { - if (chars[i].data[p] < BITMAP_ALPHA_THRESHOLD) chars[i].data[p] = 0; - else chars[i].data[p] = 255; + if (((unsigned char *)chars[i].image.data)[p] < BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0; + else ((unsigned char *)chars[i].image.data)[p] = 255; } } - chars[i].rec.width = (float)chw; - chars[i].rec.height = (float)chh; + // Load characters images + chars[i].image.width = chw; + chars[i].image.height = chh; + chars[i].image.mipmaps = 1; + chars[i].image.format = UNCOMPRESSED_GRAYSCALE; + chars[i].offsetY += (int)((float)ascent*scaleFactor); // Get bounding box for character (may be offset to account for chars that dip above or below the line) @@ -554,19 +579,24 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c // Generate image font atlas using chars info // NOTE: Packing method: 0-Default, 1-Skyline #if defined(SUPPORT_FILEFORMAT_TTF) -Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod) +Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCount, int fontSize, int padding, int packMethod) { Image atlas = { 0 }; + *charRecs = NULL; + // In case no chars count provided we suppose default of 95 - charsCount = (charsCount > 0)? charsCount : 95; + charsCount = (charsCount > 0) ? charsCount : 95; + + // NOTE: Rectangles memory is loaded here! + Rectangle *recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); // Calculate image size based on required pixel area // NOTE 1: Image is forced to be squared and POT... very conservative! // NOTE 2: SDF font characters already contain an internal padding, // so image size would result bigger than default font type float requiredArea = 0; - for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].rec.width + 2*padding)*(chars[i].rec.height + 2*padding)); + for (int i = 0; i < charsCount; i++) requiredArea += ((chars[i].image.width + 2*padding)*(chars[i].image.height + 2*padding)); float guessSize = sqrtf(requiredArea)*1.25f; int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT @@ -588,21 +618,24 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi for (int i = 0; i < charsCount; i++) { // Copy pixel data from fc.data to atlas - for (int y = 0; y < (int)chars[i].rec.height; y++) + for (int y = 0; y < chars[i].image.height; y++) { - for (int x = 0; x < (int)chars[i].rec.width; x++) + for (int x = 0; x < chars[i].image.width; x++) { - ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; + ((unsigned char *)atlas.data)[(offsetY + y)*atlas.width + (offsetX + x)] = ((unsigned char *)chars[i].image.data)[y*chars[i].image.width + x]; } } - chars[i].rec.x = (float)offsetX; - chars[i].rec.y = (float)offsetY; + // Fill chars rectangles in atlas info + recs[i].x = (float)offsetX; + recs[i].y = (float)offsetY; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; // Move atlas position X for next character drawing - offsetX += ((int)chars[i].rec.width + 2*padding); + offsetX += (chars[i].image.width + 2*padding); - if (offsetX >= (atlas.width - (int)chars[i].rec.width - padding)) + if (offsetX >= (atlas.width - chars[i].image.width - padding)) { offsetX = padding; @@ -629,8 +662,8 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi for (int i = 0; i < charsCount; i++) { rects[i].id = i; - rects[i].w = (int)chars[i].rec.width + 2*padding; - rects[i].h = (int)chars[i].rec.height + 2*padding; + rects[i].w = chars[i].image.width + 2*padding; + rects[i].h = chars[i].image.height + 2*padding; } // Package rectangles into atlas @@ -638,17 +671,20 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi for (int i = 0; i < charsCount; i++) { - chars[i].rec.x = rects[i].x + (float)padding; - chars[i].rec.y = rects[i].y + (float)padding; + // It return char rectangles in atlas + recs[i].x = rects[i].x + (float)padding; + recs[i].y = rects[i].y + (float)padding; + recs[i].width = (float)chars[i].image.width; + recs[i].height = (float)chars[i].image.height; if (rects[i].was_packed) { // Copy pixel data from fc.data to atlas - for (int y = 0; y < (int)chars[i].rec.height; y++) + for (int y = 0; y < chars[i].image.height; y++) { - for (int x = 0; x < (int)chars[i].rec.width; x++) + for (int x = 0; x < chars[i].image.width; x++) { - ((unsigned char *)atlas.data)[(rects[i].y + padding + y)*atlas.width + (rects[i].x + padding + x)] = chars[i].data[y*(int)chars[i].rec.width + x]; + ((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]; } } } @@ -664,7 +700,7 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi // Convert image data from GRAYSCALE to GRAY_ALPHA // WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation - unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(imageSize*imageSize*sizeof(unsigned char)*2); // Two channels + unsigned char *dataGrayAlpha = (unsigned char *)RL_MALLOC(atlas.width*atlas.height*sizeof(unsigned char)*2); // Two channels for (int i = 0, k = 0; i < atlas.width*atlas.height; i++, k += 2) { @@ -675,6 +711,8 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi RL_FREE(atlas.data); atlas.data = dataGrayAlpha; atlas.format = UNCOMPRESSED_GRAY_ALPHA; + + *charRecs = recs; return atlas; } @@ -686,10 +724,11 @@ void UnloadFont(Font font) // NOTE: Make sure spriteFont is not default font (fallback) if (font.texture.id != GetFontDefault().texture.id) { - for (int i = 0; i < font.charsCount; i++) RL_FREE(font.chars[i].data); + for (int i = 0; i < font.charsCount; i++) UnloadImage(font.chars[i].image); UnloadTexture(font.texture); RL_FREE(font.chars); + RL_FREE(font.recs); TraceLog(LOG_DEBUG, "Unloaded sprite font data"); } @@ -717,11 +756,13 @@ void DrawFPS(int posX, int posY) DrawText(TextFormat("%2i FPS", fps), posX, posY, 20, LIME); } -// Returns next codepoint in a UTF8 encoded `text` scanning until '\0' is found. When a invalid UTF8 byte is encountered we exit as soon -// as possible and a `?`(0x3f) codepoint is returned. `count` will hold the total number of bytes processed. -// NOTE: the standard says U+FFFD should be returned in case of errors but that character is not supported by the default font in raylib +// Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found +// When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib // TODO: optimize this code for speed!! -int GetNextCodepoint(const char *text, int *count) +int GetNextCodepoint(const char *text, int *bytesProcessed) { /* UTF8 specs from https://www.ietf.org/rfc/rfc3629.txt @@ -737,40 +778,40 @@ int GetNextCodepoint(const char *text, int *count) // NOTE: on decode errors we return as soon as possible - int c = 0x3f; // Codepoint (defaults to `?`) - int o = (unsigned char)(text[0]); // The first UTF8 octet - *count = 1; + int code = 0x3f; // Codepoint (defaults to '?') + int octet = (unsigned char)(text[0]); // The first UTF8 octet + *bytesProcessed = 1; - if (o <= 0x7f) + if (octet <= 0x7f) { // Only one octet (ASCII range x00-7F) - c = text[0]; + code = text[0]; } - else if ((o & 0xe0) == 0xc0) + else if ((octet & 0xe0) == 0xc0) { // Two octets // [0]xC2-DF [1]UTF8-tail(x80-BF) - unsigned char o1 = text[1]; + unsigned char octet1 = text[1]; - if ((o1 == '\0') || ((o1 >> 6) != 2)) { *count = 2; return c; } // Unexpected sequence + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence - if ((o >= 0xc2) && (o <= 0xdf)) + if ((octet >= 0xc2) && (octet <= 0xdf)) { - c = ((o & 0x1f) << 6) | (o1 & 0x3f); - *count = 2; + code = ((octet & 0x1f) << 6) | (octet1 & 0x3f); + *bytesProcessed = 2; } } - else if ((o & 0xf0) == 0xe0) + else if ((octet & 0xf0) == 0xe0) { // Three octets - unsigned char o1 = text[1]; - unsigned char o2 = '\0'; + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; - if ((o1 == '\0') || ((o1 >> 6) != 2)) { *count = 2; return c; } // Unexpected sequence + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence - o2 = text[2]; + octet2 = text[2]; - if ((o2 == '\0') || ((o2 >> 6) != 2)) { *count = 3; return c; } // Unexpected sequence + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence /* [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) @@ -779,33 +820,33 @@ int GetNextCodepoint(const char *text, int *count) [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) */ - if (((o == 0xe0) && !((o1 >= 0xa0) && (o1 <= 0xbf))) || - ((o == 0xed) && !((o1 >= 0x80) && (o1 <= 0x9f)))) { *count = 2; return c; } + if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || + ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *bytesProcessed = 2; return code; } - if ((o >= 0xe0) && (0 <= 0xef)) + if ((octet >= 0xe0) && (0 <= 0xef)) { - c = ((o & 0xf) << 12) | ((o1 & 0x3f) << 6) | (o2 & 0x3f); - *count = 3; + code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); + *bytesProcessed = 3; } } - else if ((o & 0xf8) == 0xf0) + else if ((octet & 0xf8) == 0xf0) { // Four octets - if (o > 0xf4) return c; + if (octet > 0xf4) return code; - unsigned char o1 = text[1]; - unsigned char o2 = '\0'; - unsigned char o3 = '\0'; + unsigned char octet1 = text[1]; + unsigned char octet2 = '\0'; + unsigned char octet3 = '\0'; - if ((o1 == '\0') || ((o1 >> 6) != 2)) { *count = 2; return c; } // Unexpected sequence + if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { *bytesProcessed = 2; return code; } // Unexpected sequence - o2 = text[2]; + octet2 = text[2]; - if ((o2 == '\0') || ((o2 >> 6) != 2)) { *count = 3; return c; } // Unexpected sequence + if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { *bytesProcessed = 3; return code; } // Unexpected sequence - o3 = text[3]; + octet3 = text[3]; - if ((o3 == '\0') || ((o3 >> 6) != 2)) { *count = 4; return c; } // Unexpected sequence + if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { *bytesProcessed = 4; return code; } // Unexpected sequence /* [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail @@ -813,19 +854,19 @@ int GetNextCodepoint(const char *text, int *count) [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail */ - if (((o == 0xf0) && !((o1 >= 0x90) && (o1 <= 0xbf))) || - ((o == 0xf4) && !((o1 >= 0x80) && (o1 <= 0x8f)))) { *count = 2; return c; } // Unexpected sequence + if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || + ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { *bytesProcessed = 2; return code; } // Unexpected sequence - if (o >= 0xf0) + if (octet >= 0xf0) { - c = ((o & 0x7) << 18) | ((o1 & 0x3f) << 12) | ((o2 & 0x3f) << 6) | (o3 & 0x3f); - *count = 4; + code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); + *bytesProcessed = 4; } } - if (c > 0x10ffff) c = 0x3f; // Codepoints after U+10ffff are invalid + if (code > 0x10ffff) code = 0x3f; // Codepoints after U+10ffff are invalid - return c; + return code; } @@ -863,13 +904,14 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f for (int i = 0; i < length; i++) { - int next = 1; + int next = 0; letter = GetNextCodepoint(&text[i], &next); - // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set `next = 1` - if(letter == 0x3f) next = 1; index = GetGlyphIndex(font, letter); - i += next - 1; + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set 'next = 1' + if (letter == 0x3f) next = 1; + i += (next - 1); if (letter == '\n') { @@ -881,14 +923,14 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f { if (letter != ' ') { - DrawTexturePro(font.texture, font.chars[index].rec, + DrawTexturePro(font.texture, font.recs[index], (Rectangle){ position.x + textOffsetX + font.chars[index].offsetX*scaleFactor, position.y + textOffsetY + font.chars[index].offsetY*scaleFactor, - font.chars[index].rec.width*scaleFactor, - font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); + font.recs[index].width*scaleFactor, + font.recs[index].height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); } - if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.chars[index].rec.width*scaleFactor + spacing); + if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); else textOffsetX += ((float)font.chars[index].advanceX*scaleFactor + spacing); } } @@ -901,8 +943,7 @@ void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, flo } // Draw text using font inside rectangle limits with support for text selection -void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, - int selectStart, int selectLength, Color selectText, Color selectBack) +void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectText, Color selectBack) { int length = strlen(text); int textOffsetX = 0; // Offset between characters @@ -916,39 +957,38 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f enum { MEASURE_STATE = 0, DRAW_STATE = 1 }; int state = wordWrap? MEASURE_STATE : DRAW_STATE; - int startLine = -1; // Index where to begin drawing (where a line begins) - int endLine = -1; // Index where to stop drawing (where a line ends) - int lastk = -1; // Holds last value of the character position + int startLine = -1; // Index where to begin drawing (where a line begins) + int endLine = -1; // Index where to stop drawing (where a line ends) + int lastk = -1; // Holds last value of the character position + for (int i = 0, k = 0; i < length; i++, k++) { int glyphWidth = 0; - int next = 1; + int next = 0; letter = GetNextCodepoint(&text[i], &next); - // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set `next = 1` - if(letter == 0x3f) next = 1; index = GetGlyphIndex(font, letter); + + // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; i += next - 1; if (letter != '\n') - { + { glyphWidth = (font.chars[index].advanceX == 0)? - (int)(font.chars[index].rec.width*scaleFactor + spacing): + (int)(font.recs[index].width*scaleFactor + spacing): (int)(font.chars[index].advanceX*scaleFactor + spacing); } - // NOTE: When wordWrap is ON we first measure how much of the text we can draw - // before going outside of the `rec` container. We store this info inside - // `startLine` and `endLine` then we change states, draw the text between those two - // variables then change states again and again recursively until the end of the text - // (or until we get outside of the container). - // When wordWrap is OFF we don't need the measure state so we go to the drawing - // state immediately and begin drawing on the next line before we can get outside - // the container. + // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container + // We store this info in startLine and endLine, then we change states, draw the text between those two variables + // and change states again and again recursively until the end of the text (or until we get outside of the container). + // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately + // and begin drawing on the next line before we can get outside the container. if (state == MEASURE_STATE) { - // TODO: there are multiple types of `spaces` in UNICODE, maybe it's a good idea to add support for more - // see: http://jkorpela.fi/chars/spaces.html + // TODO: there are multiple types of spaces in UNICODE, maybe it's a good idea to add support for more + // See: http://jkorpela.fi/chars/spaces.html if ((letter == ' ') || (letter == '\t') || (letter == '\n')) endLine = i; if ((textOffsetX + glyphWidth + 1) >= rec.width) @@ -979,7 +1019,6 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f lastk = k - 1; k = tmp; } - } else { @@ -1001,7 +1040,7 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break; - //draw selected + // Draw selected bool isGlyphSelected = false; if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) { @@ -1010,14 +1049,14 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f isGlyphSelected = true; } - //draw glyph + // Draw glyph if ((letter != ' ') && (letter != '\t')) { - DrawTexturePro(font.texture, font.chars[index].rec, + DrawTexturePro(font.texture, font.recs[index], (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor, rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor, - font.chars[index].rec.width*scaleFactor, - font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, + font.recs[index].width*scaleFactor, + font.recs[index].height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, (!isGlyphSelected)? tint : selectText); } } @@ -1076,19 +1115,19 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing { lenCounter++; - int next = 1; + int next = 0; letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); // NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set `next = 1` - if(letter == 0x3f) next = 1; + // but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1 + if (letter == 0x3f) next = 1; i += next - 1; if (letter != '\n') { - index = GetGlyphIndex(font, letter); if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; - else textWidth += (font.chars[index].rec.width + font.chars[index].offsetX); + else textWidth += (font.recs[index].width + font.chars[index].offsetX); } else { @@ -1103,7 +1142,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing if (tempTextWidth < textWidth) tempTextWidth = textWidth; - Vector2 vec; + Vector2 vec = { 0 }; vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure vec.y = textHeight*scaleFactor; @@ -1155,20 +1194,24 @@ unsigned int TextLength(const char *text) return length; } -// Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` is found. -// NOTE: If a invalid UTF8 sequence is encountered a `?`(0x3f) codepoint is counted instead. +// Returns total number of characters(codepoints) in a UTF8 encoded text, until '\0' is found +// NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead unsigned int TextCountCodepoints(const char *text) { unsigned int len = 0; - char* ptr = (char*)&text[0]; - while(*ptr != '\0') + char *ptr = (char *)&text[0]; + + while (*ptr != '\0') { int next = 0; int letter = GetNextCodepoint(ptr, &next); - if(letter == 0x3f) ptr += 1; + + if (letter == 0x3f) ptr += 1; else ptr += next; - ++len; + + len++; } + return len; } @@ -1286,17 +1329,28 @@ char *TextInsert(const char *text, const char *insert, int position) // REQUIRES: strcat() const char *TextJoin(const char **textList, int count, const char *delimiter) { - // TODO: Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH - static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 }; memset(text, 0, MAX_TEXT_BUFFER_LENGTH); + int totalLength = 0; int delimiterLen = strlen(delimiter); for (int i = 0; i < count; i++) { - strcat(text, textList[i]); - if ((delimiterLen > 0) && (i < (count - 1))) strcat(text, delimiter); + int textListLength = strlen(textList[i]); + + // Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH + if ((totalLength + textListLength) < MAX_TEXT_BUFFER_LENGTH) + { + strcat(text, textList[i]); + totalLength += textListLength; + + if ((delimiterLen > 0) && (i < (count - 1))) + { + strcat(text, delimiter); + totalLength += delimiterLen; + } + } } return text; @@ -1513,27 +1567,26 @@ static Font LoadBMFont(const char *fileName) TraceLog(LOG_DEBUG, "[%s] Font texture loading path: %s", fileName, texPath); Image imFont = LoadImage(texPath); + Image imFontAlpha = ImageCopy(imFont); if (imFont.format == UNCOMPRESSED_GRAYSCALE) { - Image imCopy = ImageCopy(imFont); + for (int i = 0; i < imFontAlpha.width*imFontAlpha.height; i++) ((unsigned char *)imFontAlpha.data)[i] = 0xff; - for (int i = 0; i < imCopy.width*imCopy.height; i++) ((unsigned char *)imCopy.data)[i] = 0xff; - - ImageAlphaMask(&imCopy, imFont); - font.texture = LoadTextureFromImage(imCopy); - UnloadImage(imCopy); + ImageAlphaMask(&imFontAlpha, imFont); + font.texture = LoadTextureFromImage(imFontAlpha); } else font.texture = LoadTextureFromImage(imFont); - + UnloadImage(imFont); - RL_FREE(texPath); + RL_FREE(texPath); // Fill font characters info data font.baseSize = fontSize; font.charsCount = charsCount; font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); + font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; @@ -1542,16 +1595,22 @@ static Font LoadBMFont(const char *fileName) fgets(buffer, MAX_BUFFER_SIZE, fntFile); sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); + + // Get character rectangle in the font atlas texture + font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; // Save data properly in sprite font font.chars[i].value = charId; - font.chars[i].rec = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; font.chars[i].offsetX = charOffsetX; font.chars[i].offsetY = charOffsetY; font.chars[i].advanceX = charAdvanceX; - font.chars[i].data = NULL; + + // Fill character image data from imFont data + font.chars[i].image = ImageFromImage(imFontAlpha, font.recs[i]); } + UnloadImage(imFontAlpha); + fclose(fntFile); if (font.texture.id == 0) diff --git a/src/textures.c b/src/textures.c index d9ad1ad0e..53e22341b 100644 --- a/src/textures.c +++ b/src/textures.c @@ -888,6 +888,16 @@ Image ImageCopy(Image image) return newImage; } +// Create an image from another image piece +Image ImageFromImage(Image image, Rectangle rec) +{ + Image result = ImageCopy(image); + + ImageCrop(&result, rec); + + return result; +} + // Convert image to POT (power-of-two) // NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) void ImageToPOT(Image *image, Color fillColor) @@ -1274,7 +1284,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layoutType) // TODO: Image formating does not work with compressed textures! } - for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size }); + for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size }, WHITE); cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format); if (cubemap.id == 0) TraceLog(LOG_WARNING, "Cubemap image could not be loaded."); @@ -1476,7 +1486,7 @@ void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, i Rectangle srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height }; Rectangle dstRec = { (float)offsetX, (float)offsetY, srcRec.width, srcRec.height }; - ImageDraw(&imTemp, *image, srcRec, dstRec); + ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); ImageFormat(&imTemp, image->format); UnloadImage(*image); *image = imTemp; @@ -1507,7 +1517,7 @@ void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, i dstRec.y = 0.0f; } - ImageDraw(&imTemp, *image, srcRec, dstRec); + ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE); ImageFormat(&imTemp, image->format); UnloadImage(*image); *image = imTemp; @@ -1757,7 +1767,8 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount) } // Draw an image (source) within an image (destination) -void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) +// NOTE: Color tint is applied to source image +void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint) { // Security check to avoid program crash if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || @@ -1823,7 +1834,8 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) UnloadImage(srcCopy); // Source copy not required any more - Vector4 fsrc, fdst, fout; // float based versions of pixel data + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint // Blit pixels, copy source image into destination // TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping @@ -1835,6 +1847,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); + + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); @@ -1885,65 +1900,44 @@ Image ImageText(const char *text, int fontSize, Color color) Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint) { int length = strlen(text); - int posX = 0; + int index; // Index position in sprite font - unsigned char character; // Current character + int letter = 0; // Current character + int positionX = 0; // Image drawing position + // NOTE: Text image is generated at font base size, later scaled to desired font size Vector2 imSize = MeasureTextEx(font, text, (float)font.baseSize, spacing); - TraceLog(LOG_DEBUG, "Text Image size: %f, %f", imSize.x, imSize.y); - - // NOTE: glGetTexImage() not available in OpenGL ES - // TODO: This is horrible, retrieving font texture from GPU!!! - // Define ImageFont struct? or include Image spritefont in Font struct? - Image imFont = GetTextureData(font.texture); - - ImageFormat(&imFont, UNCOMPRESSED_R8G8B8A8); // Make sure image format could be properly colored! - - ImageColorTint(&imFont, tint); // Apply color tint to font - // Create image to store text Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); - + for (int i = 0; i < length; i++) { - if ((unsigned char)text[i] == '\n') + int next = 0; + letter = GetNextCodepoint(&text[i], &next); + index = GetGlyphIndex(font, letter); + + if (letter == 0x3f) next = 1; + i += (next - 1); + + if (letter == '\n') { // TODO: Support line break } else { - if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! + if (letter != ' ') { - // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) - character = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)character); - i++; - } - else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! - { - // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) - character = (unsigned char)text[i + 1]; - index = GetGlyphIndex(font, (int)character + 64); - i++; - } - else index = GetGlyphIndex(font, (unsigned char)text[i]); - - CharInfo letter = font.chars[index]; - - if ((unsigned char)text[i] != ' ') - { - ImageDraw(&imText, imFont, letter.rec, (Rectangle){ (float)(posX + letter.offsetX), - (float)letter.offsetY, (float)letter.rec.width, (float)letter.rec.height }); + ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, font.chars[index].image.width, font.chars[index].image.height }, + (Rectangle){ (float)(positionX + font.chars[index].offsetX),(float)font.chars[index].offsetY, + font.chars[index].image.width, font.chars[index].image.height }, tint); } - if (letter.advanceX == 0) posX += (int)(letter.rec.width + spacing); - else posX += letter.advanceX + (int)spacing; + if (font.chars[index].advanceX == 0) positionX += (int)(font.recs[index].width + spacing); + else positionX += font.chars[index].advanceX + (int)spacing; } } - UnloadImage(imFont); - // Scale image depending on text size if (fontSize > imSize.y) { @@ -1965,7 +1959,7 @@ void ImageDrawRectangle(Image *dst, Rectangle rec, Color color) if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec); + ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); UnloadImage(imRec); } @@ -1993,7 +1987,7 @@ void ImageDrawTextEx(Image *dst, Vector2 position, Font font, const char *text, Rectangle srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height }; Rectangle dstRec = { position.x, position.y, (float)imText.width, (float)imText.height }; - ImageDraw(dst, imText, srcRec, dstRec); + ImageDraw(dst, imText, srcRec, dstRec, WHITE); UnloadImage(imText); }