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
#endif
#include <stdlib.h>
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);

View file

@ -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

View file

@ -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],

View file

@ -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!

View file

@ -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

View file

@ -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)
{
@ -676,6 +712,8 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi
atlas.data = dataGrayAlpha;
atlas.format = UNCOMPRESSED_GRAY_ALPHA;
*charRecs = recs;
return atlas;
}
#endif
@ -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;
@ -1543,15 +1596,21 @@ static Font LoadBMFont(const char *fileName)
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)

View file

@ -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
@ -1836,6 +1848,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);
if (fout.w <= 0.0f)
@ -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);
}