Skip to content

[rtext] Introducing styled texts #4818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions examples/text/text_styled.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*******************************************************************************************
*
* raylib [text] example - Text styled
*
* Example complexity rating: [★☆☆☆] 1/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Wagner Barongello (@SultansOfCode)
*
********************************************************************************************/

#include "raylib.h"

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;

InitWindow(screenWidth, screenHeight, "raylib [text] example - text styled");

SetTargetFPS(60); // Set our game to run at 60 frames-per-second

Color colors[3] = { RED, GREEN, BLUE };
int colorCount = sizeof(colors) / sizeof(Color);
//--------------------------------------------------------------------------------------

// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
// TODO: Update your variables here
//----------------------------------------------------------------------------------

// Draw
//----------------------------------------------------------------------------------
BeginDrawing();

ClearBackground(SKYBLUE);

DrawTextStyled("This changes the \0032foreground color", 200, 80, 20, colors, colorCount);

DrawTextStyled("This changes the \0042background color", 200, 120, 20, colors, colorCount);

DrawTextStyled("This changes the \0031\0042foreground and background colors", 200, 160, 20, colors, colorCount);

DrawTextStyled("\0031\0042This \015restores the colors to the default ones", 200, 200, 20, colors, colorCount);

DrawTextStyled("\0031\0042This \022inverts\022 the colors", 200, 240, 20, colors, colorCount);

EndDrawing();
//----------------------------------------------------------------------------------
}

// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------

return 0;
}
16 changes: 10 additions & 6 deletions src/raylib.h
Original file line number Diff line number Diff line change
Expand Up @@ -1479,16 +1479,20 @@ RLAPI void DrawFPS(int posX, int posY);
RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font)
RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters
RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation)
RLAPI void DrawTextStyled(const char *text, int posX, int posY, int fontSize, const Color *colors, int colorCount); // Draw styled text (using default font)
RLAPI void DrawTextStyledEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, const Color *colors, int colorCount); // Draw styled text using font and additional parameters
RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint)
RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint)

// Text font info functions
RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks
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 codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found
RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found
RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found
RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks
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 MeasureTextStyled(const char *text, int fontSize); // Measure styled string width for default font
RLAPI Vector2 MeasureTextStyledEx(Font font, const char *text, float fontSize, float spacing); // Measure styled string size for Font
RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found
RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found
RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found

// Text codepoints management functions (unicode characters)
RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array
Expand Down
219 changes: 219 additions & 0 deletions src/rtext.c
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,131 @@ void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin,
rlPopMatrix();
}

// Draw styled text
void DrawTextStyled(const char *text, int posX, int posY, int fontSize, const Color *colors, int colorCount)
{
if (GetFontDefault().texture.id != 0)
{
Vector2 position = { (float)posX, (float)posY };

int defaultFontSize = 10;
if (fontSize < defaultFontSize) fontSize = defaultFontSize;
int spacing = fontSize/defaultFontSize;

DrawTextStyledEx(GetFontDefault(), text, position, (float)fontSize, (float)spacing, colors, colorCount);
}
}

// Draw styled text using Font
void DrawTextStyledEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, const Color *colors, int colorCount)
{
if (font.texture.id == 0) font = GetFontDefault();

int size = TextLength(text);

Color defaultForeground = colorCount > 0 ? colors[0] : BLACK;
Color defaultBackground = { 0, 0, 0, 0 };

Color foreground = defaultForeground;
Color background = defaultBackground;

float textOffsetY = 0.0f;
float textOffsetX = 0.0f;

float scaleFactor = fontSize/font.baseSize;

for (int i = 0; i < size;)
{
int codepointByteCount = 0;
int codepoint = GetCodepointNext(&text[i], &codepointByteCount);

if (codepoint == '\n')
{
textOffsetY += (fontSize + textLineSpacing);
textOffsetX = 0.0f;
}
else
{
if (codepoint == '\003' || codepoint == '\004')
{
bool isForeground = codepoint == '\003';
char colorBuffer[16] = { 0 };
int colorBufferIndex = 0;

i += codepointByteCount;

codepoint = GetCodepointNext(&text[i], &codepointByteCount);

while (codepoint >= '0' && codepoint <= '9' && i < size && colorBufferIndex < sizeof(colorBuffer) - 1)
{
colorBuffer[colorBufferIndex++] = codepoint;

i += codepointByteCount;

codepoint = GetCodepointNext(&text[i], &codepointByteCount);
}

if (colorBufferIndex > 0)
{
int colorIndex = atoi(&colorBuffer);

if (colorIndex >= 0)
{
if (isForeground)
{
foreground = colorIndex < colorCount ? colors[colorIndex] : defaultForeground;
}
else
{
background = colorIndex < colorCount ? colors[colorIndex] : defaultBackground;
}
}
}

continue;
}
else if (codepoint == '\015') {
foreground = defaultForeground;
background = defaultBackground;

i += codepointByteCount;

continue;
}
else if (codepoint == '\022') {
Color temp = foreground;

foreground = background;
background = temp;

i += codepointByteCount;

continue;
}

int index = GetGlyphIndex(font, codepoint);
float increaseX = 0.0f;

if (font.glyphs[index].advanceX == 0) increaseX = ((float)font.recs[index].width*scaleFactor + spacing);
else increaseX += ((float)font.glyphs[index].advanceX*scaleFactor + spacing);

if (background.a > 0)
{
DrawRectangle(position.x + textOffsetX, position.y + textOffsetY, increaseX, fontSize + textLineSpacing, background);
}

if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, foreground);
}

textOffsetX += increaseX;
}

i += codepointByteCount;
}
}

// Draw one character (codepoint)
void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint)
{
Expand Down Expand Up @@ -1338,6 +1463,100 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing
return textSize;
}

// Measure styled string width for default font
int MeasureTextStyled(const char *text, int fontSize)
{
Vector2 textSize = { 0.0f, 0.0f };

// Check if default font has been loaded
if (GetFontDefault().texture.id != 0)
{
int defaultFontSize = 10; // Default Font chars height in pixel
if (fontSize < defaultFontSize) fontSize = defaultFontSize;
int spacing = fontSize/defaultFontSize;

textSize = MeasureTextStyledEx(GetFontDefault(), text, (float)fontSize, (float)spacing);
}

return (int)textSize.x;
}

// Measure styled string size for Font
Vector2 MeasureTextStyledEx(Font font, const char *text, float fontSize, float spacing)
{
Vector2 textSize = { 0 };

if ((isGpuReady && (font.texture.id == 0)) ||
(text == NULL) || (text[0] == '\0')) return textSize; // Security check

int size = TextLength(text); // Get size in bytes of text
int tempByteCounter = 0; // Used to count longer text line num chars
int byteCounter = 0;

float textWidth = 0.0f;
float tempTextWidth = 0.0f; // Used to count longer text line width

float textHeight = fontSize;
float scaleFactor = fontSize/(float)font.baseSize;

int letter = 0; // Current character
int index = 0; // Index position in sprite font

for (int i = 0; i < size;)
{
int codepointByteCount = 0;
letter = GetCodepointNext(&text[i], &codepointByteCount);

i += codepointByteCount;

if (letter == '\015' || letter == '\022')
{
continue;
}
else if (letter == '\003' || letter == '\004')
{
letter = GetCodepointNext(&text[i], &codepointByteCount);

while (letter >= '0' && letter <= '9' && i < size)
{
i += codepointByteCount;

letter = GetCodepointNext(&text[i], &codepointByteCount);
}

continue;
}

byteCounter++;

if (letter != '\n')
{
index = GetGlyphIndex(font, letter);

if (font.glyphs[index].advanceX > 0) textWidth += font.glyphs[index].advanceX;
else textWidth += (font.recs[index].width + font.glyphs[index].offsetX);
}
else
{
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
byteCounter = 0;
textWidth = 0;

// NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup
textHeight += (fontSize + textLineSpacing);
}

if (tempByteCounter < byteCounter) tempByteCounter = byteCounter;
}

if (tempTextWidth < textWidth) tempTextWidth = textWidth;

textSize.x = tempTextWidth*scaleFactor + (float)((tempByteCounter - 1)*spacing);
textSize.y = textHeight;

return textSize;
}

// Get index position for a unicode character on font
// NOTE: If codepoint is not found in the font it fallbacks to '?'
int GetGlyphIndex(Font font, int codepoint)
Expand Down