diff options
| author | victorfisac <[email protected]> | 2016-11-01 23:11:36 +0100 |
|---|---|---|
| committer | victorfisac <[email protected]> | 2016-11-01 23:11:36 +0100 |
| commit | 80f6b2f9635d8e4707b528337c39c0ba7bf9cf5f (patch) | |
| tree | c9587d0b1e84a89a3e9c8acb1ae16a3aeede4f78 /src/text.c | |
| parent | 2a158c47955d2d849e939152e0c174b02c500104 (diff) | |
| parent | 64f67f6e9f414a54dfc3fb519b892ecd5517f2cf (diff) | |
| download | raylib-80f6b2f9635d8e4707b528337c39c0ba7bf9cf5f.tar.gz raylib-80f6b2f9635d8e4707b528337c39c0ba7bf9cf5f.zip | |
Merge remote-tracking branch 'refs/remotes/raysan5/develop' into develop
Diffstat (limited to 'src/text.c')
| -rw-r--r-- | src/text.c | 212 |
1 files changed, 145 insertions, 67 deletions
@@ -4,7 +4,7 @@ * * Basic functions to load SpriteFonts and draw Text * -* Copyright (c) 2014 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -30,9 +30,10 @@ #include <stdarg.h> // Required for: va_list, va_start(), vfprintf(), va_end() #include <stdio.h> // Required for: FILE, fopen(), fclose(), fscanf(), feof(), rewind(), fgets() -#include "utils.h" // Required for: GetExtension() +#include "utils.h" // Required for: GetExtension(), GetNextPOT() // Following libs are used on LoadTTF() +//#define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include "external/stb_truetype.h" // Required for: stbtt_BakeFontBitmap() @@ -43,7 +44,6 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define FONT_FIRST_CHAR 32 // NOTE: Expected first char for a sprite font #define MAX_FORMATTEXT_LENGTH 64 #define MAX_SUBTEXT_LENGTH 64 @@ -68,12 +68,12 @@ static SpriteFont defaultFont; // Default font provided by raylib //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- +static int GetCharIndex(SpriteFont font, int letter); + static SpriteFont LoadImageFont(Image image, Color key, int firstChar); // Load a Image font file (XNA style) static SpriteFont LoadRBMF(const char *fileName); // Load a rBMF font file (raylib BitMap Font) static SpriteFont LoadBMFont(const char *fileName); // Load a BMFont file (AngelCode font file) - -// Generate a sprite font image from TTF data -static SpriteFont LoadTTF(const char *fileName, int fontSize, int firstChar, int numChars); +static SpriteFont LoadTTF(const char *fileName, int fontSize, int numChars, int *fontChars); // Load spritefont from TTF data extern void LoadDefaultFont(void); extern void UnloadDefaultFont(void); @@ -198,7 +198,7 @@ extern void LoadDefaultFont(void) for (int i = 0; i < defaultFont.numChars; i++) { - defaultFont.charValues[i] = FONT_FIRST_CHAR + i; // First char is 32 + defaultFont.charValues[i] = 32 + i; // First char is 32 defaultFont.charRecs[i].x = currentPosX; defaultFont.charRecs[i].y = charsDivisor + currentLine*(charsHeight + charsDivisor); @@ -249,17 +249,18 @@ SpriteFont LoadSpriteFont(const char *fileName) // Default hardcoded values for ttf file loading #define DEFAULT_TTF_FONTSIZE 32 // Font first character (32 - space) #define DEFAULT_TTF_NUMCHARS 95 // ASCII 32..126 is 95 glyphs + #define DEFAULT_FIRST_CHAR 32 // Expected first char for image spritefont SpriteFont spriteFont = { 0 }; // Check file extension if (strcmp(GetExtension(fileName),"rbmf") == 0) spriteFont = LoadRBMF(fileName); - else if (strcmp(GetExtension(fileName),"ttf") == 0) spriteFont = LoadTTF(fileName, DEFAULT_TTF_FONTSIZE, FONT_FIRST_CHAR, DEFAULT_TTF_NUMCHARS); + else if (strcmp(GetExtension(fileName),"ttf") == 0) spriteFont = LoadSpriteFontTTF(fileName, DEFAULT_TTF_FONTSIZE, 0, NULL); else if (strcmp(GetExtension(fileName),"fnt") == 0) spriteFont = LoadBMFont(fileName); else { Image image = LoadImage(fileName); - if (image.data != NULL) spriteFont = LoadImageFont(image, MAGENTA, FONT_FIRST_CHAR); + if (image.data != NULL) spriteFont = LoadImageFont(image, MAGENTA, DEFAULT_FIRST_CHAR); UnloadImage(image); } @@ -268,6 +269,38 @@ SpriteFont LoadSpriteFont(const char *fileName) TraceLog(WARNING, "[%s] SpriteFont could not be loaded, using default font", fileName); spriteFont = GetDefaultFont(); } + else SetTextureFilter(spriteFont.texture, FILTER_POINT); // By default we set point filter (best performance) + + return spriteFont; +} + +// Load SpriteFont from TTF file with custom parameters +// NOTE: You can pass an array with desired characters, those characters should be available in the font +// if array is NULL, default char set is selected 32..126 +SpriteFont LoadSpriteFontTTF(const char *fileName, int fontSize, int numChars, int *fontChars) +{ + SpriteFont spriteFont = { 0 }; + + if (strcmp(GetExtension(fileName),"ttf") == 0) + { + if ((fontChars == NULL) || (numChars == 0)) + { + int totalChars = 95; // Default charset [32..126] + + int *defaultFontChars = (int *)malloc(totalChars*sizeof(int)); + + for (int i = 0; i < totalChars; i++) defaultFontChars[i] = i + 32; // Default first character: SPACE[32] + + spriteFont = LoadTTF(fileName, fontSize, totalChars, defaultFontChars); + } + else spriteFont = LoadTTF(fileName, fontSize, numChars, fontChars); + } + + if (spriteFont.texture.id == 0) + { + TraceLog(WARNING, "[%s] SpriteFont could not be generated, using default font", fileName); + spriteFont = GetDefaultFont(); + } return spriteFont; } @@ -293,13 +326,17 @@ void UnloadSpriteFont(SpriteFont spriteFont) // NOTE: chars spacing is proportional to fontSize void DrawText(const char *text, int posX, int posY, int fontSize, Color color) { - Vector2 position = { (float)posX, (float)posY }; + // Check if default font has been loaded + if (defaultFont.texture.id != 0) + { + Vector2 position = { (float)posX, (float)posY }; - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize/defaultFontSize; + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; - DrawTextEx(defaultFont, text, position, (float)fontSize, spacing, color); + DrawTextEx(GetDefaultFont(), text, position, (float)fontSize, spacing, color); + } } // Draw text using SpriteFont @@ -321,22 +358,18 @@ void DrawTextEx(SpriteFont spriteFont, const char *text, Vector2 position, float for (int i = 0; i < length; i++) { - // TODO: Right now we are supposing characters that follow a continous order and start at FONT_FIRST_CHAR, - // this sytem can be improved to support any characters order and init value... - // An intermediate table could be created to link char values with predefined char position index in chars rectangle array - if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK! { // Support UTF-8 encoded values from [0xc2 0x80] -> [0xc2 0xbf](¿) letter = (unsigned char)text[i + 1]; - rec = spriteFont.charRecs[letter - FONT_FIRST_CHAR]; + rec = spriteFont.charRecs[GetCharIndex(spriteFont, (int)letter)]; i++; } else if ((unsigned char)text[i] == 0xc3) // UTF-8 encoding identification HACK! { // Support UTF-8 encoded values from [0xc3 0x80](À) -> [0xc3 0xbf](ÿ) letter = (unsigned char)text[i + 1]; - rec = spriteFont.charRecs[letter - FONT_FIRST_CHAR + 64]; + rec = spriteFont.charRecs[GetCharIndex(spriteFont, (int)letter + 64)]; i++; } else @@ -348,17 +381,19 @@ void DrawTextEx(SpriteFont spriteFont, const char *text, Vector2 position, float textOffsetX = 0; rec.x = -1; } - else rec = spriteFont.charRecs[(int)text[i] - FONT_FIRST_CHAR]; + else rec = spriteFont.charRecs[GetCharIndex(spriteFont, (int)text[i])]; } - if (rec.x > 0) + if (rec.x >= 0) { - DrawTexturePro(spriteFont.texture, rec, (Rectangle){ position.x + textOffsetX + spriteFont.charOffsets[(int)text[i] - FONT_FIRST_CHAR].x*scaleFactor, - position.y + textOffsetY + spriteFont.charOffsets[(int)text[i] - FONT_FIRST_CHAR].y*scaleFactor, + int index = GetCharIndex(spriteFont, (int)text[i]); + + DrawTexturePro(spriteFont.texture, rec, (Rectangle){ position.x + textOffsetX + spriteFont.charOffsets[index].x*scaleFactor, + position.y + textOffsetY + spriteFont.charOffsets[index].y*scaleFactor, rec.width*scaleFactor, rec.height*scaleFactor} , (Vector2){ 0, 0 }, 0.0f, tint); - if (spriteFont.charAdvanceX[(int)text[i] - FONT_FIRST_CHAR] == 0) textOffsetX += (rec.width*scaleFactor + spacing); - else textOffsetX += (spriteFont.charAdvanceX[(int)text[i] - FONT_FIRST_CHAR]*scaleFactor + spacing); + if (spriteFont.charAdvanceX[index] == 0) textOffsetX += (rec.width*scaleFactor + spacing); + else textOffsetX += (spriteFont.charAdvanceX[index]*scaleFactor + spacing); } } } @@ -404,13 +439,17 @@ const char *SubText(const char *text, int position, int length) // Measure string width for default font int MeasureText(const char *text, int fontSize) { - Vector2 vec; + Vector2 vec = { 0.0f, 0.0f }; - int defaultFontSize = 10; // Default Font chars height in pixel - if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize/defaultFontSize; + // Check if default font has been loaded + if (defaultFont.texture.id != 0) + { + int defaultFontSize = 10; // Default Font chars height in pixel + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + int spacing = fontSize/defaultFontSize; - vec = MeasureTextEx(defaultFont, text, fontSize, spacing); + vec = MeasureTextEx(GetDefaultFont(), text, fontSize, spacing); + } return (int)vec.x; } @@ -434,8 +473,10 @@ Vector2 MeasureTextEx(SpriteFont spriteFont, const char *text, int fontSize, int if (text[i] != '\n') { - if (spriteFont.charAdvanceX[(int)text[i] - FONT_FIRST_CHAR] != 0) textWidth += spriteFont.charAdvanceX[(int)text[i] - FONT_FIRST_CHAR]; - else textWidth += (spriteFont.charRecs[(int)text[i] - FONT_FIRST_CHAR].width + spriteFont.charOffsets[(int)text[i] - FONT_FIRST_CHAR].x); + int index = GetCharIndex(spriteFont, (int)text[i]); + + if (spriteFont.charAdvanceX[index] != 0) textWidth += spriteFont.charAdvanceX[index]; + else textWidth += (spriteFont.charRecs[index].width + spriteFont.charOffsets[index].x); } else { @@ -492,7 +533,28 @@ void DrawFPS(int posX, int posY) // Module specific Functions Definition //---------------------------------------------------------------------------------- -// Load a Image font file (XNA style) +static int GetCharIndex(SpriteFont font, int letter) +{ +//#define UNORDERED_CHARSET +#if defined(UNORDERED_CHARSET) + int index = 0; + + for (int i = 0; i < font.numChars; i++) + { + if (font.charValues[i] == letter) + { + index = i; + break; + } + } + + return index; +#else + return (letter - 32); +#endif +} + +// Load an Image font file (XNA style) static SpriteFont LoadImageFont(Image image, Color key, int firstChar) { #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) @@ -503,8 +565,8 @@ static SpriteFont LoadImageFont(Image image, Color key, int firstChar) int x = 0; int y = 0; - // Default number of characters expected supported - #define MAX_FONTCHARS 128 + // Default number of characters supported + #define MAX_FONTCHARS 256 // We allocate a temporal arrays for chars data measures, // once we get the actual number of chars, we copy data to a sized arrays @@ -565,15 +627,24 @@ static SpriteFont LoadImageFont(Image image, Color key, int firstChar) xPosToRead = charSpacing; } - free(pixels); - TraceLog(DEBUG, "SpriteFont data parsed correctly from image"); + + // NOTE: We need to remove key color borders from image to avoid weird + // artifacts on texture scaling when using FILTER_BILINEAR or FILTER_TRILINEAR + for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK; + + // Create a new image with the processed color data (key color replaced by BLANK) + Image fontClear = LoadImageEx(pixels, image.width, image.height); + + free(pixels); // Free pixels array memory // Create spritefont with all data parsed from image SpriteFont spriteFont = { 0 }; - spriteFont.texture = LoadTextureFromImage(image); // Convert loaded image to OpenGL texture + spriteFont.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture spriteFont.numChars = 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 @@ -803,8 +874,13 @@ static SpriteFont LoadBMFont(const char *fileName) strncat(texPath, texFileName, strlen(texFileName)); TraceLog(DEBUG, "[%s] Font texture loading path: %s", fileName, texPath); + + Image imFont = LoadImage(texPath); + + if (imFont.format == UNCOMPRESSED_GRAYSCALE) ImageAlphaMask(&imFont, imFont); - font.texture = LoadTexture(texPath); + font.texture = LoadTextureFromImage(imFont); + font.size = fontSize; font.numChars = numChars; font.charValues = (int *)malloc(numChars*sizeof(int)); @@ -812,22 +888,18 @@ static SpriteFont LoadBMFont(const char *fileName) font.charOffsets = (Vector2 *)malloc(numChars*sizeof(Vector2)); font.charAdvanceX = (int *)malloc(numChars*sizeof(int)); + UnloadImage(imFont); + free(texPath); int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; - bool unorderedChars = false; - int firstChar = 0; - for (int i = 0; i < numChars; i++) { fgets(buffer, MAX_BUFFER_SIZE, fntFile); sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); - if (i == 0) firstChar = charId; - else if (i != (charId - firstChar)) unorderedChars = true; - // Save data properly in sprite font font.charValues[i] = charId; font.charRecs[i] = (Rectangle){ charX, charY, charWidth, charHeight }; @@ -837,11 +909,7 @@ static SpriteFont LoadBMFont(const char *fileName) fclose(fntFile); - if (firstChar != FONT_FIRST_CHAR) TraceLog(WARNING, "BMFont not supported: expected SPACE(32) as first character, falling back to default font"); - else if (unorderedChars) TraceLog(WARNING, "BMFont not supported: unordered chars data, falling back to default font"); - - // NOTE: Font data could be not ordered by charId: 32,33,34,35... raylib does not support unordered BMFonts - if ((firstChar != FONT_FIRST_CHAR) || (unorderedChars) || (font.texture.id == 0)) + if (font.texture.id == 0) { UnloadSpriteFont(font); font = GetDefaultFont(); @@ -853,14 +921,17 @@ static SpriteFont LoadBMFont(const char *fileName) // Generate a sprite font from TTF file data (font size required) // TODO: Review texture packing method and generation (use oversampling) -static SpriteFont LoadTTF(const char *fileName, int fontSize, int firstChar, int numChars) +static SpriteFont LoadTTF(const char *fileName, int fontSize, int numChars, int *fontChars) { - // NOTE: Generated font uses some hardcoded values - #define FONT_TEXTURE_WIDTH 512 // Font texture width - #define FONT_TEXTURE_HEIGHT 512 // Font texture height + // NOTE: Font texture size is predicted (being as much conservative as possible) + // Predictive method consist of supposing same number of chars by line-column (sqrtf) + // and a maximum character width of 3/4 of fontSize... it worked ok with all my tests... + int textureSize = GetNextPOT(ceil((float)fontSize*3/4)*ceil(sqrtf((float)numChars))); + + TraceLog(INFO, "TTF spritefont loading: Predicted texture size: %ix%i", textureSize, textureSize); unsigned char *ttfBuffer = (unsigned char *)malloc(1 << 25); - unsigned char *dataBitmap = (unsigned char *)malloc(FONT_TEXTURE_WIDTH*FONT_TEXTURE_HEIGHT*sizeof(unsigned char)); // One channel bitmap returned! + unsigned char *dataBitmap = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)); // One channel bitmap returned! stbtt_bakedchar *charData = (stbtt_bakedchar *)malloc(sizeof(stbtt_bakedchar)*numChars); SpriteFont font = { 0 }; @@ -869,40 +940,47 @@ static SpriteFont LoadTTF(const char *fileName, int fontSize, int firstChar, int if (ttfFile == NULL) { - TraceLog(WARNING, "[%s] FNT file could not be opened", fileName); + TraceLog(WARNING, "[%s] TTF file could not be opened", fileName); return font; } fread(ttfBuffer, 1, 1<<25, ttfFile); + + if (fontChars[0] != 32) TraceLog(WARNING, "TTF spritefont loading: first character is not SPACE(32) character"); // NOTE: Using stb_truetype crappy packing method, no guarante the font fits the image... - stbtt_BakeFontBitmap(ttfBuffer,0, fontSize, dataBitmap, FONT_TEXTURE_WIDTH, FONT_TEXTURE_HEIGHT, firstChar, numChars, charData); + // TODO: Replace this function by a proper packing method and support random chars order, + // we already receive a list (fontChars) with the ordered expected characters + int result = stbtt_BakeFontBitmap(ttfBuffer, 0, fontSize, dataBitmap, textureSize, textureSize, fontChars[0], numChars, charData); + //if (result > 0) TraceLog(INFO, "TTF spritefont loading: first unused row of generated bitmap: %i", result); + if (result < 0) TraceLog(WARNING, "TTF spritefont loading: Not all the characters fit in the font"); + free(ttfBuffer); // Convert image data from grayscale to to UNCOMPRESSED_GRAY_ALPHA - unsigned char *dataGrayAlpha = (unsigned char *)malloc(FONT_TEXTURE_WIDTH*FONT_TEXTURE_HEIGHT*sizeof(unsigned char)*2); // Two channels - int k = 0; + unsigned char *dataGrayAlpha = (unsigned char *)malloc(textureSize*textureSize*sizeof(unsigned char)*2); // Two channels - for (int i = 0; i < FONT_TEXTURE_WIDTH*FONT_TEXTURE_HEIGHT; i++) + for (int i = 0, k = 0; i < textureSize*textureSize; i++, k += 2) { dataGrayAlpha[k] = 255; dataGrayAlpha[k + 1] = dataBitmap[i]; - - k += 2; } free(dataBitmap); // Sprite font generation from TTF extracted data Image image; - image.width = FONT_TEXTURE_WIDTH; - image.height = FONT_TEXTURE_HEIGHT; + image.width = textureSize; + image.height = textureSize; image.mipmaps = 1; image.format = UNCOMPRESSED_GRAY_ALPHA; image.data = dataGrayAlpha; - + font.texture = LoadTextureFromImage(image); + + //WritePNG("generated_ttf_image.png", (unsigned char *)image.data, image.width, image.height, 2); + UnloadImage(image); // Unloads dataGrayAlpha font.size = fontSize; @@ -914,7 +992,7 @@ static SpriteFont LoadTTF(const char *fileName, int fontSize, int firstChar, int for (int i = 0; i < font.numChars; i++) { - font.charValues[i] = i + firstChar; + font.charValues[i] = fontChars[i]; font.charRecs[i].x = (int)charData[i].x0; font.charRecs[i].y = (int)charData[i].y0; |
