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.
This commit is contained in:
Ray 2019-07-24 15:05:14 +02:00
parent 543c0ba30d
commit b4d67499a7
7 changed files with 264 additions and 208 deletions

View file

@ -17,6 +17,8 @@
#define GLSL_VERSION 100 #define GLSL_VERSION 100
#endif #endif
#include <stdlib.h>
int main(void) int main(void)
{ {
// Initialization // Initialization
@ -37,7 +39,7 @@ int main(void)
// Parameters > font size: 16, no chars array provided (0), chars count: 95 (autogenerate chars array) // 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); 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) // 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); fontDefault.texture = LoadTextureFromImage(atlas);
UnloadImage(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) // 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); 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) // 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); fontSDF.texture = LoadTextureFromImage(atlas);
UnloadImage(atlas); UnloadImage(atlas);

View file

@ -32,7 +32,7 @@ int main(void)
Image parrots = LoadImage("resources/parrots.png"); // Load image in CPU memory (RAM) Image parrots = LoadImage("resources/parrots.png"); // Load image in CPU memory (RAM)
// Draw one image over the other with a scaling of 1.5f // 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 ImageCrop(&parrots, (Rectangle){ 0, 50, parrots.width, parrots.height - 100 }); // Crop resulting image
UnloadImage(cat); // Unload image from RAM UnloadImage(cat); // Unload image from RAM

View file

@ -152,7 +152,7 @@ void InitGameplayScreen(void)
{ {
ImageDraw(&imWords, imWordsBase, ImageDraw(&imWords, imWordsBase,
(Rectangle){ 0, 0, imWordsBase.width, imWordsBase.height }, (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], ImageDrawTextEx(&imWords,(Vector2){ imWordsBase.width/2 - MeasureTextEx(fontMessage, codingWords[i],
fontMessage.baseSize, 0).x/2, imWordsBase.height*i }, fontMessage, codingWords[i], fontMessage.baseSize, 0).x/2, imWordsBase.height*i }, fontMessage, codingWords[i],

View file

@ -263,18 +263,18 @@ typedef struct NPatchInfo {
// Font character info // Font character info
typedef struct CharInfo { typedef struct CharInfo {
int value; // Character value (Unicode) int value; // Character value (Unicode)
Rectangle rec; // Character rectangle in sprite font
int offsetX; // Character offset X when drawing int offsetX; // Character offset X when drawing
int offsetY; // Character offset Y when drawing int offsetY; // Character offset Y when drawing
int advanceX; // Character advance position X int advanceX; // Character advance position X
unsigned char *data; // Character pixel data (grayscale) Image image; // Character image data
} CharInfo; } CharInfo;
// Font type, includes texture and charSet array data // Font type, includes texture and charSet array data
typedef struct Font { typedef struct Font {
Texture2D texture; // Font texture
int baseSize; // Base size (default chars height) int baseSize; // Base size (default chars height)
int charsCount; // Number of characters int charsCount; // Number of characters
Texture2D texture; // Characters texture atlas
Rectangle *recs; // Characters rectangles in texture
CharInfo *chars; // Characters info data CharInfo *chars; // Characters info data
} Font; } Font;
@ -1100,6 +1100,7 @@ RLAPI void UpdateTexture(Texture2D texture, const void *pixels);
// Image manipulation functions // Image manipulation functions
RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) 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 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 ImageFormat(Image *image, int newFormat); // Convert image data to desired format
RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image 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 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 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 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 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 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) 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 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 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 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) RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM)
// Text drawing functions // 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 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 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 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 RLAPI int GetNextCodepoint(const char *text, int *bytesProcessed); // Returns next codepoint in a UTF8 encoded string
// NOTE: 0x3f(`?`) is returned on failure, `count` will hold the total number of bytes processed // NOTE: 0x3f('?') is returned on failure
// Text strings management functions // Text strings management functions
// NOTE: Some strings allocate memory internally for returned strings, just be careful! // NOTE: Some strings allocate memory internally for returned strings, just be careful!

View file

@ -1522,7 +1522,7 @@ static Texture2D GetShapesTexture(void)
{ {
#if defined(SUPPORT_FONT_TEXTURE) #if defined(SUPPORT_FONT_TEXTURE)
texShapes = GetFontDefault().texture; // Use font texture white character 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 // 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 }; recTexShapes = (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 };
#else #else

View file

@ -193,13 +193,12 @@ extern void LoadFontDefault(void)
if (counter > 512) counter = 0; // Security check... if (counter > 512) counter = 0; // Security check...
} }
Image image = LoadImageEx(imagePixels, imWidth, imHeight); Image imFont = LoadImageEx(imagePixels, imWidth, imHeight);
ImageFormat(&image, UNCOMPRESSED_GRAY_ALPHA); ImageFormat(&imFont, UNCOMPRESSED_GRAY_ALPHA);
RL_FREE(imagePixels); RL_FREE(imagePixels);
defaultFont.texture = LoadTextureFromImage(image); defaultFont.texture = LoadTextureFromImage(imFont);
UnloadImage(image);
// Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, charsCount
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -207,6 +206,7 @@ extern void LoadFontDefault(void)
// Allocate space for our characters info data // Allocate space for our characters info data
// NOTE: This memory should be freed at end! --> CloseWindow() // NOTE: This memory should be freed at end! --> CloseWindow()
defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo)); defaultFont.chars = (CharInfo *)RL_MALLOC(defaultFont.charsCount*sizeof(CharInfo));
defaultFont.recs = (Rectangle *)RL_MALLOC(defaultFont.charsCount*sizeof(Rectangle));
int currentLine = 0; int currentLine = 0;
int currentPosX = charsDivisor; int currentPosX = charsDivisor;
@ -216,12 +216,12 @@ extern void LoadFontDefault(void)
{ {
defaultFont.chars[i].value = 32 + i; // First char is 32 defaultFont.chars[i].value = 32 + i; // First char is 32
defaultFont.chars[i].rec.x = (float)currentPosX; defaultFont.recs[i].x = (float)currentPosX;
defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
defaultFont.chars[i].rec.width = (float)charsWidth[i]; defaultFont.recs[i].width = (float)charsWidth[i];
defaultFont.chars[i].rec.height = (float)charsHeight; 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) if (testPosX >= defaultFont.texture.width)
{ {
@ -229,8 +229,8 @@ extern void LoadFontDefault(void)
currentPosX = 2*charsDivisor + charsWidth[i]; currentPosX = 2*charsDivisor + charsWidth[i];
testPosX = currentPosX; testPosX = currentPosX;
defaultFont.chars[i].rec.x = (float)charsDivisor; defaultFont.recs[i].x = (float)charsDivisor;
defaultFont.chars[i].rec.y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor)); defaultFont.recs[i].y = (float)(charsDivisor + currentLine*(charsHeight + charsDivisor));
} }
else currentPosX = testPosX; else currentPosX = testPosX;
@ -238,9 +238,14 @@ extern void LoadFontDefault(void)
defaultFont.chars[i].offsetX = 0; defaultFont.chars[i].offsetX = 0;
defaultFont.chars[i].offsetY = 0; defaultFont.chars[i].offsetY = 0;
defaultFont.chars[i].advanceX = 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); 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 // Unload raylib default font
extern void UnloadFontDefault(void) extern void UnloadFontDefault(void)
{ {
for (int i = 0; i < defaultFont.charsCount; i++) UnloadImage(defaultFont.chars[i].image);
UnloadTexture(defaultFont.texture); UnloadTexture(defaultFont.texture);
RL_FREE(defaultFont.chars); RL_FREE(defaultFont.chars);
RL_FREE(defaultFont.recs);
} }
#endif // SUPPORT_DEFAULT_FONT #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 defined(SUPPORT_FILEFORMAT_TTF)
if (font.chars != NULL) 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); 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); UnloadImage(atlas);
} }
else font = GetFontDefault(); else font = GetFontDefault();
#else #else
UnloadFont(font);
font = GetFontDefault(); font = GetFontDefault();
#endif #endif
@ -415,25 +431,30 @@ Font LoadFontFromImage(Image image, Color key, int firstChar)
spriteFont.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture spriteFont.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture
spriteFont.charsCount = index; spriteFont.charsCount = index;
UnloadImage(fontClear); // Unload processed image once converted to texture
// We got tempCharValues and tempCharsRecs populated with chars data // We got tempCharValues and tempCharsRecs populated with chars data
// Now we move temp data to sized charValues and charRecs arrays // Now we move temp data to sized charValues and charRecs arrays
spriteFont.chars = (CharInfo *)RL_MALLOC(spriteFont.charsCount*sizeof(CharInfo)); 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++) for (int i = 0; i < spriteFont.charsCount; i++)
{ {
spriteFont.chars[i].value = tempCharValues[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) // 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].offsetX = 0;
spriteFont.chars[i].offsetY = 0; spriteFont.chars[i].offsetY = 0;
spriteFont.chars[i].advanceX = 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"); 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_GetCodepointBitmapBox() -- how big the bitmap must be
// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide // 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); 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].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 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].data = NULL; else chars[i].image.data = NULL;
if (type == FONT_BITMAP) 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 // NOTE: For optimum results, bitmap font should be generated at base pixel size
for (int p = 0; p < chw*chh; p++) for (int p = 0; p < chw*chh; p++)
{ {
if (chars[i].data[p] < BITMAP_ALPHA_THRESHOLD) chars[i].data[p] = 0; if (((unsigned char *)chars[i].image.data)[p] < BITMAP_ALPHA_THRESHOLD) ((unsigned char *)chars[i].image.data)[p] = 0;
else chars[i].data[p] = 255; else ((unsigned char *)chars[i].image.data)[p] = 255;
} }
} }
chars[i].rec.width = (float)chw; // Load characters images
chars[i].rec.height = (float)chh; 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); 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) // 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 // Generate image font atlas using chars info
// NOTE: Packing method: 0-Default, 1-Skyline // NOTE: Packing method: 0-Default, 1-Skyline
#if defined(SUPPORT_FILEFORMAT_TTF) #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 }; Image atlas = { 0 };
*charRecs = NULL;
// In case no chars count provided we suppose default of 95 // 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 // Calculate image size based on required pixel area
// NOTE 1: Image is forced to be squared and POT... very conservative! // NOTE 1: Image is forced to be squared and POT... very conservative!
// NOTE 2: SDF font characters already contain an internal padding, // NOTE 2: SDF font characters already contain an internal padding,
// so image size would result bigger than default font type // so image size would result bigger than default font type
float requiredArea = 0; 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; float guessSize = sqrtf(requiredArea)*1.25f;
int imageSize = (int)powf(2, ceilf(logf((float)guessSize)/logf(2))); // Calculate next POT 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++) for (int i = 0; i < charsCount; i++)
{ {
// Copy pixel data from fc.data to atlas // 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; // Fill chars rectangles in atlas info
chars[i].rec.y = (float)offsetY; 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 // 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; offsetX = padding;
@ -629,8 +662,8 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi
for (int i = 0; i < charsCount; i++) for (int i = 0; i < charsCount; i++)
{ {
rects[i].id = i; rects[i].id = i;
rects[i].w = (int)chars[i].rec.width + 2*padding; rects[i].w = chars[i].image.width + 2*padding;
rects[i].h = (int)chars[i].rec.height + 2*padding; rects[i].h = chars[i].image.height + 2*padding;
} }
// Package rectangles into atlas // 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++) for (int i = 0; i < charsCount; i++)
{ {
chars[i].rec.x = rects[i].x + (float)padding; // It return char rectangles in atlas
chars[i].rec.y = rects[i].y + (float)padding; 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) if (rects[i].was_packed)
{ {
// Copy pixel data from fc.data to atlas // 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 // Convert image data from GRAYSCALE to GRAY_ALPHA
// WARNING: ImageAlphaMask(&atlas, atlas) does not work in this case, requires manual operation // 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) 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); RL_FREE(atlas.data);
atlas.data = dataGrayAlpha; atlas.data = dataGrayAlpha;
atlas.format = UNCOMPRESSED_GRAY_ALPHA; atlas.format = UNCOMPRESSED_GRAY_ALPHA;
*charRecs = recs;
return atlas; return atlas;
} }
@ -686,10 +724,11 @@ void UnloadFont(Font font)
// NOTE: Make sure spriteFont is not default font (fallback) // NOTE: Make sure spriteFont is not default font (fallback)
if (font.texture.id != GetFontDefault().texture.id) 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); UnloadTexture(font.texture);
RL_FREE(font.chars); RL_FREE(font.chars);
RL_FREE(font.recs);
TraceLog(LOG_DEBUG, "Unloaded sprite font data"); 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); 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 // Returns next codepoint in a UTF8 encoded text, scanning until '\0' is found
// as possible and a `?`(0x3f) codepoint is returned. `count` will hold the total number of bytes processed. // When a invalid UTF8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned
// 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 // 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!! // 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 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 // NOTE: on decode errors we return as soon as possible
int c = 0x3f; // Codepoint (defaults to `?`) int code = 0x3f; // Codepoint (defaults to '?')
int o = (unsigned char)(text[0]); // The first UTF8 octet int octet = (unsigned char)(text[0]); // The first UTF8 octet
*count = 1; *bytesProcessed = 1;
if (o <= 0x7f) if (octet <= 0x7f)
{ {
// Only one octet (ASCII range x00-7F) // 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 // Two octets
// [0]xC2-DF [1]UTF8-tail(x80-BF) // [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); code = ((octet & 0x1f) << 6) | (octet1 & 0x3f);
*count = 2; *bytesProcessed = 2;
} }
} }
else if ((o & 0xf0) == 0xe0) else if ((octet & 0xf0) == 0xe0)
{ {
// Three octets // Three octets
unsigned char o1 = text[1]; unsigned char octet1 = text[1];
unsigned char o2 = '\0'; 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) [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) [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF)
*/ */
if (((o == 0xe0) && !((o1 >= 0xa0) && (o1 <= 0xbf))) || if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) ||
((o == 0xed) && !((o1 >= 0x80) && (o1 <= 0x9f)))) { *count = 2; return c; } ((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); code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);
*count = 3; *bytesProcessed = 3;
} }
} }
else if ((o & 0xf8) == 0xf0) else if ((octet & 0xf8) == 0xf0)
{ {
// Four octets // Four octets
if (o > 0xf4) return c; if (octet > 0xf4) return code;
unsigned char o1 = text[1]; unsigned char octet1 = text[1];
unsigned char o2 = '\0'; unsigned char octet2 = '\0';
unsigned char o3 = '\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 [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 [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail
*/ */
if (((o == 0xf0) && !((o1 >= 0x90) && (o1 <= 0xbf))) || if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||
((o == 0xf4) && !((o1 >= 0x80) && (o1 <= 0x8f)))) { *count = 2; return c; } // Unexpected sequence ((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); code = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f);
*count = 4; *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++) for (int i = 0; i < length; i++)
{ {
int next = 1; int next = 0;
letter = GetNextCodepoint(&text[i], &next); 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); 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') if (letter == '\n')
{ {
@ -881,14 +923,14 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f
{ {
if (letter != ' ') if (letter != ' ')
{ {
DrawTexturePro(font.texture, font.chars[index].rec, DrawTexturePro(font.texture, font.recs[index],
(Rectangle){ position.x + textOffsetX + font.chars[index].offsetX*scaleFactor, (Rectangle){ position.x + textOffsetX + font.chars[index].offsetX*scaleFactor,
position.y + textOffsetY + font.chars[index].offsetY*scaleFactor, position.y + textOffsetY + font.chars[index].offsetY*scaleFactor,
font.chars[index].rec.width*scaleFactor, font.recs[index].width*scaleFactor,
font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, tint); 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); 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 // 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, 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 selectStart, int selectLength, Color selectText, Color selectBack)
{ {
int length = strlen(text); int length = strlen(text);
int textOffsetX = 0; // Offset between characters 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 }; enum { MEASURE_STATE = 0, DRAW_STATE = 1 };
int state = wordWrap? MEASURE_STATE : DRAW_STATE; int state = wordWrap? MEASURE_STATE : DRAW_STATE;
int startLine = -1; // Index where to begin drawing (where a line begins) 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 endLine = -1; // Index where to stop drawing (where a line ends)
int lastk = -1; // Holds last value of the character position int lastk = -1; // Holds last value of the character position
for (int i = 0, k = 0; i < length; i++, k++) for (int i = 0, k = 0; i < length; i++, k++)
{ {
int glyphWidth = 0; int glyphWidth = 0;
int next = 1; int next = 0;
letter = GetNextCodepoint(&text[i], &next); 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); 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; i += next - 1;
if (letter != '\n') if (letter != '\n')
{ {
glyphWidth = (font.chars[index].advanceX == 0)? 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); (int)(font.chars[index].advanceX*scaleFactor + spacing);
} }
// NOTE: When wordWrap is ON we first measure how much of the text we can draw // NOTE: When wordWrap is ON we first measure how much of the text we can draw before going outside of the rec container
// before going outside of the `rec` container. We store this info inside // We store this info in startLine and endLine, then we change states, draw the text between those two variables
// `startLine` and `endLine` then we change states, draw the text between those two // and change states again and again recursively until the end of the text (or until we get outside of the container).
// variables then change states again and again recursively until the end of the text // When wordWrap is OFF we don't need the measure state so we go to the drawing state immediately
// (or until we get outside of the container). // and begin drawing on the next line before we can get outside 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) if (state == MEASURE_STATE)
{ {
// TODO: there are multiple types of `spaces` in UNICODE, maybe it's a good idea to add support for more // 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 // See: http://jkorpela.fi/chars/spaces.html
if ((letter == ' ') || (letter == '\t') || (letter == '\n')) endLine = i; if ((letter == ' ') || (letter == '\t') || (letter == '\n')) endLine = i;
if ((textOffsetX + glyphWidth + 1) >= rec.width) 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; lastk = k - 1;
k = tmp; k = tmp;
} }
} }
else 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; if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break;
//draw selected // Draw selected
bool isGlyphSelected = false; bool isGlyphSelected = false;
if ((selectStart >= 0) && (k >= selectStart) && (k < (selectStart + selectLength))) 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; isGlyphSelected = true;
} }
//draw glyph // Draw glyph
if ((letter != ' ') && (letter != '\t')) 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, (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor,
rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor, rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor,
font.chars[index].rec.width*scaleFactor, font.recs[index].width*scaleFactor,
font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f, font.recs[index].height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f,
(!isGlyphSelected)? tint : selectText); (!isGlyphSelected)? tint : selectText);
} }
} }
@ -1076,19 +1115,19 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing
{ {
lenCounter++; lenCounter++;
int next = 1; int next = 0;
letter = GetNextCodepoint(&text[i], &next); 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) // 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` // 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; if (letter == 0x3f) next = 1;
i += next - 1; i += next - 1;
if (letter != '\n') if (letter != '\n')
{ {
index = GetGlyphIndex(font, letter);
if (font.chars[index].advanceX != 0) textWidth += font.chars[index].advanceX; 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 else
{ {
@ -1103,7 +1142,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing
if (tempTextWidth < textWidth) tempTextWidth = textWidth; if (tempTextWidth < textWidth) tempTextWidth = textWidth;
Vector2 vec; Vector2 vec = { 0 };
vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure vec.x = tempTextWidth*scaleFactor + (float)((tempLen - 1)*spacing); // Adds chars spacing to measure
vec.y = textHeight*scaleFactor; vec.y = textHeight*scaleFactor;
@ -1155,20 +1194,24 @@ unsigned int TextLength(const char *text)
return length; return length;
} }
// Returns total number of characters(codepoints) in a UTF8 encoded `text` until `\0` is found. // 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. // NOTE: If an invalid UTF8 sequence is encountered a '?'(0x3f) codepoint is counted instead
unsigned int TextCountCodepoints(const char *text) unsigned int TextCountCodepoints(const char *text)
{ {
unsigned int len = 0; unsigned int len = 0;
char* ptr = (char*)&text[0]; char *ptr = (char *)&text[0];
while(*ptr != '\0')
while (*ptr != '\0')
{ {
int next = 0; int next = 0;
int letter = GetNextCodepoint(ptr, &next); int letter = GetNextCodepoint(ptr, &next);
if(letter == 0x3f) ptr += 1;
if (letter == 0x3f) ptr += 1;
else ptr += next; else ptr += next;
++len;
len++;
} }
return len; return len;
} }
@ -1286,17 +1329,28 @@ char *TextInsert(const char *text, const char *insert, int position)
// REQUIRES: strcat() // REQUIRES: strcat()
const char *TextJoin(const char **textList, int count, const char *delimiter) 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 }; static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 };
memset(text, 0, MAX_TEXT_BUFFER_LENGTH); memset(text, 0, MAX_TEXT_BUFFER_LENGTH);
int totalLength = 0;
int delimiterLen = strlen(delimiter); int delimiterLen = strlen(delimiter);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
strcat(text, textList[i]); int textListLength = strlen(textList[i]);
if ((delimiterLen > 0) && (i < (count - 1))) strcat(text, delimiter);
// 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; return text;
@ -1513,27 +1567,26 @@ static Font LoadBMFont(const char *fileName)
TraceLog(LOG_DEBUG, "[%s] Font texture loading path: %s", fileName, texPath); TraceLog(LOG_DEBUG, "[%s] Font texture loading path: %s", fileName, texPath);
Image imFont = LoadImage(texPath); Image imFont = LoadImage(texPath);
Image imFontAlpha = ImageCopy(imFont);
if (imFont.format == UNCOMPRESSED_GRAYSCALE) 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(&imFontAlpha, imFont);
font.texture = LoadTextureFromImage(imFontAlpha);
ImageAlphaMask(&imCopy, imFont);
font.texture = LoadTextureFromImage(imCopy);
UnloadImage(imCopy);
} }
else font.texture = LoadTextureFromImage(imFont); else font.texture = LoadTextureFromImage(imFont);
UnloadImage(imFont); UnloadImage(imFont);
RL_FREE(texPath);
RL_FREE(texPath);
// Fill font characters info data // Fill font characters info data
font.baseSize = fontSize; font.baseSize = fontSize;
font.charsCount = charsCount; font.charsCount = charsCount;
font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); 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; 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); 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", 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); &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 // Save data properly in sprite font
font.chars[i].value = charId; 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].offsetX = charOffsetX;
font.chars[i].offsetY = charOffsetY; font.chars[i].offsetY = charOffsetY;
font.chars[i].advanceX = charAdvanceX; 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); fclose(fntFile);
if (font.texture.id == 0) if (font.texture.id == 0)

View file

@ -888,6 +888,16 @@ Image ImageCopy(Image image)
return newImage; 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) // Convert image to POT (power-of-two)
// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) // NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5)
void ImageToPOT(Image *image, Color fillColor) 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! // 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); cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format);
if (cubemap.id == 0) TraceLog(LOG_WARNING, "Cubemap image could not be loaded."); 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 srcRec = { 0.0f, 0.0f, (float)image->width, (float)image->height };
Rectangle dstRec = { (float)offsetX, (float)offsetY, srcRec.width, srcRec.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); ImageFormat(&imTemp, image->format);
UnloadImage(*image); UnloadImage(*image);
*image = imTemp; *image = imTemp;
@ -1507,7 +1517,7 @@ void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, i
dstRec.y = 0.0f; dstRec.y = 0.0f;
} }
ImageDraw(&imTemp, *image, srcRec, dstRec); ImageDraw(&imTemp, *image, srcRec, dstRec, WHITE);
ImageFormat(&imTemp, image->format); ImageFormat(&imTemp, image->format);
UnloadImage(*image); UnloadImage(*image);
*image = imTemp; *image = imTemp;
@ -1757,7 +1767,8 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount)
} }
// Draw an image (source) within an image (destination) // 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 // Security check to avoid program crash
if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || 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 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 // Blit pixels, copy source image into destination
// TODO: Maybe out-of-bounds blitting could be considered here instead of so much cropping // 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]); fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]);
fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); 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); 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) Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint)
{ {
int length = strlen(text); int length = strlen(text);
int posX = 0;
int index; // Index position in sprite font 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); 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 // Create image to store text
Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK); Image imText = GenImageColor((int)imSize.x, (int)imSize.y, BLANK);
for (int i = 0; i < length; i++) 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 // TODO: Support line break
} }
else else
{ {
if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! if (letter != ' ')
{ {
// Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) ImageDraw(&imText, font.chars[index].image, (Rectangle){ 0, 0, font.chars[index].image.width, font.chars[index].image.height },
character = (unsigned char)text[i + 1]; (Rectangle){ (float)(positionX + font.chars[index].offsetX),(float)font.chars[index].offsetY,
index = GetGlyphIndex(font, (int)character); font.chars[index].image.width, font.chars[index].image.height }, tint);
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 });
} }
if (letter.advanceX == 0) posX += (int)(letter.rec.width + spacing); if (font.chars[index].advanceX == 0) positionX += (int)(font.recs[index].width + spacing);
else posX += letter.advanceX + (int)spacing; else positionX += font.chars[index].advanceX + (int)spacing;
} }
} }
UnloadImage(imFont);
// Scale image depending on text size // Scale image depending on text size
if (fontSize > imSize.y) 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; if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return;
Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); 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); 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 srcRec = { 0.0f, 0.0f, (float)imText.width, (float)imText.height };
Rectangle dstRec = { position.x, position.y, (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); UnloadImage(imText);
} }