inline styling
There is no browser build of this example yet.
Is it the same as C?
- Desktop:verified The Zig version renders a final frame that is byte-identical to the C example.
- Browser:not checked yet The browser comparison does not cover this example yet.
//! 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.