← ziglang.inraylib-zigGitHub

inline styling

★★★☆by Wagner Barongello (@SultansOfCode)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [text] example - inline styling
//!
//! Zig-API translation of examples/text/text_inline_styling.c (raylib 6.2).
//! Example contributed by Wagner Barongello (@SultansOfCode) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 Wagner Barongello (@SultansOfCode) and Ramon Santamaria (@raysan5)

const std = @import("std");
const rl = @import("raylib");

pub fn main() void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    rl.initWindow(screen_width, screen_height, "raylib [text] example - inline styling");
    defer rl.closeWindow(); // Close window and OpenGL context

    var col_random: rl.Color = .red; // Random color used on text
    var frame_counter: u32 = 0; // Used to generate a new random color every certain frames

    rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        frame_counter += 1;

        if (frame_counter % 20 == 0) {
            col_random.r = @intCast(rl.getRandomValue(0, 255));
            col_random.g = @intCast(rl.getRandomValue(0, 255));
            col_random.b = @intCast(rl.getRandomValue(0, 255));
            col_random.a = 255;
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        // Text inline styling strategy used: [ ] delimiters for format
        // - Define foreground color:      [cRRGGBBAA]
        // - Define background color:      [bRRGGBBAA]
        // - Reset formating:              [r]
        // Colors defined with [cRRGGBBAA] or [bRRGGBBAA] are multiplied by the base color alpha
        // This allows global transparency control while keeping per-section styling (ex. text fade effects)
        // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text

        drawTextStyled(rl.getFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!", .{ .x = 100, .y = 80 }, 20.0, 2.0, .black);

        drawTextStyled(rl.getFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!", .{ .x = 100, .y = 120 }, 20.0, 2.0, .black);

        drawTextStyled(rl.getFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!", .{ .x = 100, .y = 160 }, 20.0, 2.0, .black);

        drawTextStyled(rl.getFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!", .{ .x = 100, .y = 200 }, 20.0, 2.0, .{ .r = 0, .g = 0, .b = 0, .a = 100 });

        // Get formated text
        const text = rl.textFormat("Let's be [c{x:0>2}{x:0>2}{x:0>2}FF]CREATIVE[r] !!!", .{ col_random.r, col_random.g, col_random.b });
        drawTextStyled(rl.getFontDefault(), text, .{ .x = 100, .y = 240 }, 40.0, 2.0, .black);

        const text_size = measureTextStyled(rl.getFontDefault(), text, 40.0, 2.0);
        rl.drawRectangleLines(100, 240, @intFromFloat(text_size.x), @intFromFloat(text_size.y), .green);
    }
}

/// An inline style tag found at the start of a text: `[r]`, `[cRRGGBBAA]` or `[bRRGGBBAA]`.
const StyleTag = struct {
    kind: enum { reset, foreground, background },
    color: rl.Color = .blank,
    len: usize, // Bytes to skip
};

/// Parse the inline style tag at the start of `text` (which begins with '['), if any.
fn parseStyleTag(text: []const u8) ?StyleTag {
    if (text.len > 2 and text[1] == 'r' and text[2] == ']') return .{ .kind = .reset, .len = 3 };
    if (text.len > 1 and (text[1] == 'c' or text[1] == 'b')) {
        // Parse following color: hex digits up to ']'
        const hex = text[2..];
        const hex_count = for (hex, 0..) |char, n| {
            if (!std.ascii.isHex(char)) break n;
        } else hex.len;

        // Convert hex color text into actual Color
        const value = std.fmt.parseInt(u32, hex[0..hex_count], 16) catch 0;
        return .{
            .kind = if (text[1] == 'c') .foreground else .background,
            .color = rl.getColor(value),
            .len = 2 + hex_count + 1, // Skip "[c" or "[b", color value and ']'
        };
    }
    return null;
}

/// Draw text using inline styling
/// PARAM: color is the default text color, background color is BLANK by default
/// NOTE: Using input color as the base alpha multiplied to inline styles
fn drawTextStyled(font_in: rl.Font, text: [:0]const u8, position: rl.Vector2, font_size: f32, spacing: f32, color: rl.Color) void {
    const font = if (font_in.texture.id == 0) rl.getFontDefault() else font_in;

    var col_front = color;
    var col_back: rl.Color = .blank;
    const back_rec_padding = 4; // Background rectangle padding

    var text_offset_y: f32 = 0.0;
    var text_offset_x: f32 = 0.0;
    const text_line_spacing: f32 = 0.0;
    const scale_factor = font_size / @as(f32, @floatFromInt(font.baseSize));

    var i: usize = 0;
    while (i < text.len) {
        const next = rl.getCodepointNext(text[i..]);
        const codepoint = next.codepoint;

        if (codepoint == '\n') {
            text_offset_y += (font_size + text_line_spacing);
            text_offset_x = 0.0;
        } else {
            if (codepoint == '[') { // Process pipe styling
                if (parseStyleTag(text[i..])) |tag| {
                    switch (tag.kind) {
                        .reset => {
                            col_front = color;
                            col_back = .blank;
                        },
                        .foreground => col_front = tag.color,
                        .background => col_back = tag.color,
                    }
                    i += tag.len;
                    continue; // Do not draw characters
                }
            }

            const index: usize = @intCast(rl.getGlyphIndex(font, codepoint));
            const advance_x = font.glyphs.?[index].advanceX;
            const increase_x = if (advance_x == 0)
                font.recs.?[index].width * scale_factor + spacing
            else
                @as(f32, @floatFromInt(advance_x)) * scale_factor + spacing;

            // Draw background rectangle color (if required)
            if (col_back.a > 0) rl.drawRectangleRec(.{
                .x = position.x + text_offset_x,
                .y = position.y + text_offset_y - back_rec_padding,
                .width = increase_x,
                .height = font_size + 2 * back_rec_padding,
            }, col_back);

            if (codepoint != ' ' and codepoint != '\t') {
                rl.drawTextCodepoint(font, codepoint, .{ .x = position.x + text_offset_x, .y = position.y + text_offset_y }, font_size, col_front);
            }

            text_offset_x += increase_x;
        }

        i += @intCast(next.size);
    }
}

/// Measure inline styled text
/// NOTE: Measuring styled text requires skipping styling data
/// WARNING: Not considering line breaks
fn measureTextStyled(font: rl.Font, text: [:0]const u8, font_size: f32, spacing: f32) rl.Vector2 {
    if (font.texture.id == 0 or text.len == 0) return .{ .x = 0, .y = 0 }; // Security check

    var text_width: f32 = 0.0;
    const text_height = font_size;
    const scale_factor = font_size / @as(f32, @floatFromInt(font.baseSize));

    var valid_codepoint_counter: i32 = 0;

    var i: usize = 0;
    while (i < text.len) {
        const next = rl.getCodepointNext(text[i..]);
        const codepoint = next.codepoint;

        if (codepoint == '[') { // Ignore pipe inline styling
            if (parseStyleTag(text[i..])) |tag| {
                i += tag.len;
                continue; // Do not measure characters
            }
        } else if (codepoint != '\n') {
            const index: usize = @intCast(rl.getGlyphIndex(font, codepoint));
            const glyph = font.glyphs.?[index];

            if (glyph.advanceX > 0) {
                text_width += @floatFromInt(glyph.advanceX);
            } else {
                text_width += (font.recs.?[index].width + @as(f32, @floatFromInt(glyph.offsetX)));
            }

            valid_codepoint_counter += 1;
        }

        i += @intCast(next.size);
    }

    return .{
        .x = text_width * scale_factor + @as(f32, @floatFromInt(valid_codepoint_counter - 1)) * spacing,
        .y = text_height,
    };
}
/*******************************************************************************************
*
*   raylib [text] example - inline styling
*
*   Example complexity rating: [★★★☆] 3/4
*
*   Example originally created with raylib 6.0, last time updated with raylib 6.0
*
*   Example contributed by Wagner Barongello (@SultansOfCode) and reviewed by Ramon Santamaria (@raysan5)
*
*   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) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/

#include "raylib.h"

#include <stdlib.h>     // Required for: strtoul(), NULL

//----------------------------------------------------------------------------------
// Module Functions Declaration
//----------------------------------------------------------------------------------
static void DrawTextStyled(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color color);
static Vector2 MeasureTextStyled(Font font, const char *text, float fontSize, float spacing);

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

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

    Vector2 textSize = { 0 };   // Measure text box for provided font and text
    Color colRandom = RED;      // Random color used on text
    int frameCounter = 0;       // Used to generate a new random color every certain frames

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

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        frameCounter++;

        if ((frameCounter%20) == 0)
        {
            colRandom.r = (unsigned char)GetRandomValue(0, 255);
            colRandom.g = (unsigned char)GetRandomValue(0, 255);
            colRandom.b = (unsigned char)GetRandomValue(0, 255);
            colRandom.a = 255;
        }
        //----------------------------------------------------------------------------------

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

            ClearBackground(RAYWHITE);

            // Text inline styling strategy used: [ ] delimiters for format
            // - Define foreground color:      [cRRGGBBAA]
            // - Define background color:      [bRRGGBBAA]
            // - Reset formating:              [r]
            // Colors defined with [cRRGGBBAA] or [bRRGGBBAA] are multiplied by the base color alpha
            // This allows global transparency control while keeping per-section styling (ex. text fade effects)
            // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text

            DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!",
                (Vector2){ 100, 80 }, 20.0f, 2.0f, BLACK);

            DrawTextStyled(GetFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!",
                (Vector2){ 100, 120 }, 20.0f, 2.0f, BLACK);

            DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!",
                (Vector2){ 100, 160 }, 20.0f, 2.0f, BLACK);

            DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!",
                (Vector2){ 100, 200 }, 20.0f, 2.0f, (Color){ 0, 0, 0, 100 });

            // Get pointer to formated text
            const char *text = TextFormat("Let's be [c%02x%02x%02xFF]CREATIVE[r] !!!", colRandom.r, colRandom.g, colRandom.b);
            DrawTextStyled(GetFontDefault(), text, (Vector2){ 100, 240 }, 40.0f, 2.0f, BLACK);

            textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f);
            DrawRectangleLines(100, 240, (int)textSize.x, (int)textSize.y, GREEN);

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

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

    return 0;
}

//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Draw text using inline styling
// PARAM: color is the default text color, background color is BLANK by default
// NOTE: Using input color as the base alpha multiplied to inline styles
static void DrawTextStyled(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color color)
{
    // Text inline styling strategy used: [ ] delimiters for format
    // - Define foreground color:      [cRRGGBBAA]
    // - Define background color:      [bRRGGBBAA]
    // - Reset formating:              [r]
    // Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text

    if (font.texture.id == 0) font = GetFontDefault();

    int textLen = TextLength(text);

    Color colFront = color;
    Color colBack = BLANK;
    int backRecPadding = 4; // Background rectangle padding

    float textOffsetY = 0.0f;
    float textOffsetX = 0.0f;
    float textLineSpacing = 0.0f;
    float scaleFactor = fontSize/font.baseSize;

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

        if (codepoint == '\n')
        {
            textOffsetY += (fontSize + textLineSpacing);
            textOffsetX = 0.0f;
        }
        else
        {
            if (codepoint == '[') // Process pipe styling
            {
                if (((i + 2) < textLen) && (text[i + 1] == 'r') && (text[i + 2] == ']')) // Reset styling
                {
                    colFront = color;
                    colBack = BLANK;

                    i += 3;     // Skip "[r]"
                    continue;   // Do not draw characters
                }
                else if (((i + 1) < textLen) && ((text[i + 1] == 'c') || (text[i + 1] == 'b')))
                {
                    i += 2;     // Skip "[c" or "[b" to start parsing color

                    // Parse following color
                    char colHexText[9] = { 0 };
                    const char *textPtr = &text[i]; // Color should start here, let's see...

                    int colHexCount = 0;
                    while ((textPtr != NULL) && (textPtr[colHexCount] != '\0') && (textPtr[colHexCount] != ']'))
                    {
                        if (((textPtr[colHexCount] >= '0') && (textPtr[colHexCount] <= '9')) ||
                            ((textPtr[colHexCount] >= 'A') && (textPtr[colHexCount] <= 'F')) ||
                            ((textPtr[colHexCount] >= 'a') && (textPtr[colHexCount] <= 'f')))
                        {
                            colHexText[colHexCount] = textPtr[colHexCount];
                            colHexCount++;
                        }
                        else break; // Only affects while loop
                    }

                    // Convert hex color text into actual Color
                    unsigned int colHexValue = strtoul(colHexText, NULL, 16);
                    if (text[i - 1] == 'c')
                    {
						colFront = GetColor(colHexValue);
						//colFront.a *= (unsigned char)(colFront.a*(float)color.a/255.0f); // TODO: Review
					}
                    else if (text[i - 1] == 'b')
					{
						colBack = GetColor(colHexValue);
						//colBack.a *= (unsigned char)(colFront.a*(float)color.a/255.0f);
					}

                    i += (colHexCount + 1); // Skip color value retrieved and ']'
                    continue;   // Do not draw characters
                }
            }

            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);

            // Draw background rectangle color (if required)
            if (colBack.a > 0) DrawRectangleRec((Rectangle) { position.x + textOffsetX, position.y + textOffsetY - backRecPadding, increaseX, fontSize + 2*backRecPadding }, colBack);

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

            textOffsetX += increaseX;
        }

        i += codepointByteCount;
    }
}

// Measure inline styled text
// NOTE: Measuring styled text requires skipping styling data
// WARNING: Not considering line breaks
static Vector2 MeasureTextStyled(Font font, const char *text, float fontSize, float spacing)
{
    Vector2 textSize = { 0 };

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

    int textLen = TextLength(text); // Get size in bytes of text
    //float textLineSpacing = fontSize*1.5f; // Not used...

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

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

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

        if (codepoint == '[') // Ignore pipe inline styling
        {
            if (((i + 2) < textLen) && (text[i + 1] == 'r') && (text[i + 2] == ']')) // Reset styling
            {
                i += 3;     // Skip "[r]"
                continue;   // Do not measure characters
            }
            else if (((i + 1) < textLen) && ((text[i + 1] == 'c') || (text[i + 1] == 'b')))
            {
                i += 2;     // Skip "[c" or "[b" to start parsing color

                const char *textPtr = &text[i]; // Color should start here, let's see...

                int colHexCount = 0;
                while ((textPtr != NULL) && (textPtr[colHexCount] != '\0') && (textPtr[colHexCount] != ']'))
                {
                    if (((textPtr[colHexCount] >= '0') && (textPtr[colHexCount] <= '9')) ||
                        ((textPtr[colHexCount] >= 'A') && (textPtr[colHexCount] <= 'F')) ||
                        ((textPtr[colHexCount] >= 'a') && (textPtr[colHexCount] <= 'f')))
                    {
                        colHexCount++;
                    }
                    else break; // Only affects while loop
                }

                i += (colHexCount + 1); // Skip color value retrieved and ']'
                continue;   // Do not measure characters
            }
        }
        else if (codepoint != '\n')
        {
            index = GetGlyphIndex(font, codepoint);

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

            validCodepointCounter++;
            i += codepointByteCount;
        }
    }

    textSize.x = textWidth*scaleFactor + (validCodepointCounter - 1)*spacing;
    textSize.y = textHeight;

    return textSize;
}

From raylib 6.2. The original on GitHub.