From b4d67499a73af36453b7588cbd0427112a1154b2 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 24 Jul 2019 15:05:14 +0200 Subject: [PATCH] BREAKING CHANGE: Read description Changes: - Font structure has been redesigned, CharInfo structure contained character rectangle within font texture, it has not much sense, considering that it was an information relative to the font atlas generated and not the character itself, so character rectangles have been moved out from CharInfo to Font. - CharInfo included a data parameters to contain character pixel data (usually grayscale), generated on TTF font generation. It was inconsistent with other fonts types, so, now CharInfo includes directly an Image of the glyph. - REDESIGNED: GenImageFontAtlas(), additional recs parameter added, loaded and filled inside the function to export atlas characters rectangles, instead of silently modify the input CharInfo data. - REVIEWED: ImageTextEx(), funtion retrieved the font atlas image from the GPU, that was slow and problematic in several platforms. Now it uses directly the CharInfo image. Support for unicode codepoints has also been added. - REDESIGNED: ImageDraw(), now it includes an additional parameter, the color tint, not only it could be useful for several situations but also function signature is more consistent with similar functions. - ADDED: ImageFromImage() to generate a new image from a piece of another image. - REVIEWED: GetNextCodepoint(), renamed parameters to be more clear. Also all examples and games that were affected by those changes have been reviewed. --- examples/text/text_font_sdf.c | 6 +- examples/textures/textures_image_drawing.c | 2 +- games/transmission/screens/screen_gameplay.c | 2 +- src/raylib.h | 15 +- src/shapes.c | 2 +- src/text.c | 357 +++++++++++-------- src/textures.c | 88 +++-- 7 files changed, 264 insertions(+), 208 deletions(-) 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); }