Watch
2
0
Fork
You've already forked raylib-cs
0

chore: clean recommit

This commit is contained in:
tiger tiger tiger 2026-07-07 23:47:38 +02:00
commit 60ad2e7fb1
122 changed files with 23950 additions and 323 deletions

View file

@ -0,0 +1,312 @@
/*******************************************************************************************
*
* 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)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class InlineStyling : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Text / Inline Styling";
public string Title => "raylib [text] example - inline styling";
private Vector2 textSize; // Measure text box for provided font and text
private Color colRandom; // Random color used on text
private int frameCounter; // Used to generate a new random color every certain frames
public void Init()
{
textSize = new(0, 0);
colRandom = Color.Red;
frameCounter = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
frameCounter++;
if ((frameCounter % 20) == 0)
{
colRandom.R = (byte)GetRandomValue(0, 255);
colRandom.G = (byte)GetRandomValue(0, 255);
colRandom.B = (byte)GetRandomValue(0, 255);
colRandom.A = 255;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.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!!!",
new Vector2(100, 80), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!",
new Vector2(100, 120), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!",
new Vector2(100, 160), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!",
new Vector2(100, 200), 20.0f, 2.0f, new Color(0, 0, 0, 100));
// Get pointer to formated text
string text = $"Let's be [c{colRandom.R:x2}{colRandom.G:x2}{colRandom.B:x2}FF]CREATIVE[r] !!!";
DrawTextStyled(GetFontDefault(), text, new Vector2(100, 240), 40.0f, 2.0f, Color.Black);
textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f);
DrawRectangleLines(100, 240, (int)textSize.X, (int)textSize.Y, Color.Green);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// 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
private static unsafe void DrawTextStyled(Font font, string 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();
}
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int textLen = Encoding.UTF8.GetByteCount(text);
Color colFront = color;
Color colBack = Color.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(&t[i], &codepointByteCount);
if (codepoint == '\n')
{
textOffsetY += (fontSize + textLineSpacing);
textOffsetX = 0.0f;
}
else
{
if (codepoint == '[') // Process pipe styling
{
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
{
colFront = color;
colBack = Color.Blank;
i += 3; // Skip "[r]"
continue; // Do not draw characters
}
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
{
i += 2; // Skip "[c" or "[b" to start parsing color
// Parse following color
var colHexText = new StringBuilder();
int colHexCount = 0;
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
{
char ch = (char)t[i + colHexCount];
if (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'F')) ||
((ch >= 'a') && (ch <= 'f')))
{
colHexText.Append(ch);
colHexCount++;
}
else break; // Only affects while loop
}
// Convert hex color text into actual Color
uint colHexValue = colHexText.Length > 0 ? Convert.ToUInt32(colHexText.ToString(), 16) : 0;
if ((char)t[i - 1] == 'c')
{
colFront = GetColor(colHexValue);
}
else if ((char)t[i - 1] == 'b')
{
colBack = GetColor(colHexValue);
}
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 = (font.Recs[index].Width * scaleFactor + spacing);
else increaseX += (font.Glyphs[index].AdvanceX * scaleFactor + spacing);
// Draw background rectangle color (if required)
if (colBack.A > 0) DrawRectangleRec(new Rectangle(position.X + textOffsetX, position.Y + textOffsetY - backRecPadding, increaseX, fontSize + 2 * backRecPadding), colBack);
if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint(font, codepoint, new 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
private static unsafe Vector2 MeasureTextStyled(Font font, string text, float fontSize, float spacing)
{
Vector2 textSize = new(0, 0);
if ((font.Texture.Id == 0) || (text == null) || (text.Length == 0)) return textSize; // Security check
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int textLen = Encoding.UTF8.GetByteCount(text); // Get size in bytes of text
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(&t[i], &codepointByteCount);
if (codepoint == '[') // Ignore pipe inline styling
{
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
{
i += 3; // Skip "[r]"
continue; // Do not measure characters
}
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
{
i += 2; // Skip "[c" or "[b" to start parsing color
int colHexCount = 0;
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
{
char ch = (char)t[i + colHexCount];
if (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'F')) ||
((ch >= 'a') && (ch <= '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;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - inline styling");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new InlineStyling();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,530 @@
/*******************************************************************************************
*
* raylib [text] example - strings management
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by David Buzatto (@davidbuzatto) 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 David Buzatto (@davidbuzatto)
*
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class StringsManagement : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxTextLength = 100;
private const int MaxTextParticles = 100;
private const int FontSize = 30;
public string Name => "Text / Strings Management";
public string Title => "raylib [text] example - strings management";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private class TextParticle
{
public string Text;
public Rectangle Rect; // Boundary
public Vector2 Vel; // Velocity
public Vector2 Ppos; // Previous position
public float Padding;
public float BorderWidth;
public float Friction;
public float Elasticity;
public Color Color;
public bool Grabbed;
}
private List<TextParticle> textParticles;
private TextParticle grabbedTextParticle;
private Vector2 pressOffset;
public void Init()
{
textParticles = new();
grabbedTextParticle = null;
pressOffset = new(0, 0);
PrepareFirstTextParticle("raylib => fun videogames programming!");
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float delta = GetFrameTime();
Vector2 mousePos = GetMousePosition();
// Checks if a text particle was grabbed
if (IsMouseButtonPressed(MouseButton.Left))
{
for (int i = textParticles.Count - 1; i >= 0; i--)
{
TextParticle tp = textParticles[i];
if (CheckCollisionPointRec(mousePos, tp.Rect))
{
pressOffset.X = mousePos.X - tp.Rect.X;
pressOffset.Y = mousePos.Y - tp.Rect.Y;
tp.Grabbed = true;
grabbedTextParticle = tp;
break;
}
}
}
// Releases any text particle the was grabbed
if (IsMouseButtonReleased(MouseButton.Left))
{
if (grabbedTextParticle != null)
{
grabbedTextParticle.Grabbed = false;
grabbedTextParticle = null;
}
}
// Slice os shatter a text particle
if (IsMouseButtonPressed(MouseButton.Right))
{
for (int i = textParticles.Count - 1; i >= 0; i--)
{
TextParticle tp = textParticles[i];
if (CheckCollisionPointRec(mousePos, tp.Rect))
{
if (IsKeyDown(KeyboardKey.LeftShift))
{
ShatterTextParticle(tp, i);
}
else
{
SliceTextParticle(tp, i, tp.Text.Length / 2);
}
break;
}
}
}
// Shake text particles
if (IsMouseButtonPressed(MouseButton.Middle))
{
for (int i = 0; i < textParticles.Count; i++)
{
if (!textParticles[i].Grabbed)
{
textParticles[i].Vel = new Vector2(GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000));
}
}
}
// Reset using TextTo* functions
if (IsKeyPressed(KeyboardKey.One)) PrepareFirstTextParticle("raylib => fun videogames programming!");
if (IsKeyPressed(KeyboardKey.Two)) PrepareFirstTextParticle(TextToUpper("raylib => fun videogames programming!"));
if (IsKeyPressed(KeyboardKey.Three)) PrepareFirstTextParticle(TextToLower("raylib => fun videogames programming!"));
if (IsKeyPressed(KeyboardKey.Four)) PrepareFirstTextParticle(TextToPascal("raylib_fun_videogames_programming"));
if (IsKeyPressed(KeyboardKey.Five)) PrepareFirstTextParticle(TextToSnake("RaylibFunVideogamesProgramming"));
if (IsKeyPressed(KeyboardKey.Six)) PrepareFirstTextParticle(TextToCamel("raylib_fun_videogames_programming"));
// Slice by char pressed only when we have one text particle
int charPressed = GetCharPressed();
if ((charPressed >= 'A') && (charPressed <= 'z') && (textParticles.Count == 1))
{
SliceTextParticleByChar(textParticles[0], (char)charPressed);
}
// Updates each text particle state
for (int i = 0; i < textParticles.Count; i++)
{
TextParticle tp = textParticles[i];
// The text particle is not grabbed
if (!tp.Grabbed)
{
// text particle repositioning using the velocity
tp.Rect.X += tp.Vel.X * delta;
tp.Rect.Y += tp.Vel.Y * delta;
// Does the text particle hit the screen right boundary?
if ((tp.Rect.X + tp.Rect.Width) >= screenWidth)
{
tp.Rect.X = screenWidth - tp.Rect.Width; // Text particle repositioning
tp.Vel.X = -tp.Vel.X * tp.Elasticity; // Elasticity makes the text particle lose 10% of its velocity on hit
}
// Does the text particle hit the screen left boundary?
else if (tp.Rect.X <= 0)
{
tp.Rect.X = 0.0f;
tp.Vel.X = -tp.Vel.X * tp.Elasticity;
}
// The same for y axis
if ((tp.Rect.Y + tp.Rect.Height) >= screenHeight)
{
tp.Rect.Y = screenHeight - tp.Rect.Height;
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
}
else if (tp.Rect.Y <= 0)
{
tp.Rect.Y = 0.0f;
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
}
// Friction makes the text particle lose 1% of its velocity each frame
tp.Vel.X = tp.Vel.X * tp.Friction;
tp.Vel.Y = tp.Vel.Y * tp.Friction;
}
else
{
// Text particle repositioning using the mouse position
tp.Rect.X = mousePos.X - pressOffset.X;
tp.Rect.Y = mousePos.Y - pressOffset.Y;
// While the text particle is grabbed, recalculates its velocity
tp.Vel.X = (tp.Rect.X - tp.Ppos.X) / delta;
tp.Vel.Y = (tp.Rect.Y - tp.Ppos.Y) / delta;
tp.Ppos.X = tp.Rect.X;
tp.Ppos.Y = tp.Rect.Y;
// Glue text particles when dragging and pressing left ctrl
if (IsKeyDown(KeyboardKey.LeftControl))
{
for (int j = 0; j < textParticles.Count; j++)
{
if (textParticles[j] != grabbedTextParticle && grabbedTextParticle.Grabbed)
{
if (CheckCollisionRecs(grabbedTextParticle.Rect, textParticles[j].Rect))
{
GlueTextParticles(grabbedTextParticle, textParticles[j]);
grabbedTextParticle = textParticles[textParticles.Count - 1];
}
}
}
}
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < textParticles.Count; i++)
{
TextParticle tp = textParticles[i];
DrawRectangleRec(new Rectangle(tp.Rect.X - tp.BorderWidth, tp.Rect.Y - tp.BorderWidth, tp.Rect.Width + tp.BorderWidth * 2, tp.Rect.Height + tp.BorderWidth * 2), Color.Black);
DrawRectangleRec(tp.Rect, tp.Color);
DrawText(tp.Text, (int)(tp.Rect.X + tp.Padding), (int)(tp.Rect.Y + tp.Padding), FontSize, Color.Black);
}
DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, Color.DarkGray);
DrawText("slice a text particle by pressing it with the mouse right button", 10, 30, 10, Color.DarkGray);
DrawText("shatter a text particle keeping left shift pressed and pressing it with the mouse right button", 10, 50, 10, Color.DarkGray);
DrawText("glue text particles by grabbing than and keeping left control pressed", 10, 70, 10, Color.DarkGray);
DrawText("1 to 6 to reset", 10, 90, 10, Color.DarkGray);
DrawText("when you have only one text particle, you can slice it by pressing a char", 10, 110, 10, Color.DarkGray);
DrawText($"TEXT PARTICLE COUNT: {textParticles.Count}", 10, GetScreenHeight() - 30, 20, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
private void PrepareFirstTextParticle(string text)
{
TextParticle first = CreateTextParticle(
text,
GetScreenWidth() / 2.0f,
GetScreenHeight() / 2.0f,
Color.RayWhite
);
textParticles.Clear();
textParticles.Add(first);
}
private static TextParticle CreateTextParticle(string text, float x, float y, Color color)
{
TextParticle tp = new()
{
Text = "",
Rect = new Rectangle(x, y, 30, 30),
Vel = new Vector2(GetRandomValue(-200, 200), GetRandomValue(-200, 200)),
Ppos = new Vector2(0, 0),
Padding = 5.0f,
BorderWidth = 5.0f,
Friction = 0.99f,
Elasticity = 0.9f,
Color = color,
Grabbed = false
};
// Emulate C TextCopy() into a fixed size buffer
if (text.Length > MaxTextLength - 1)
{
text = text.Substring(0, MaxTextLength - 1);
}
tp.Text = text;
tp.Rect.Width = MeasureText(tp.Text, FontSize) + tp.Padding * 2;
tp.Rect.Height = FontSize + tp.Padding * 2;
return tp;
}
private void SliceTextParticle(TextParticle tp, int particlePos, int sliceLength)
{
int length = tp.Text.Length;
if ((length > 1) && ((textParticles.Count + length) < MaxTextParticles))
{
for (int i = 0; i < length; i += sliceLength)
{
string text = sliceLength == 1 ? tp.Text[i].ToString() : Subtext(tp.Text, i, sliceLength);
textParticles.Add(CreateTextParticle(
text,
tp.Rect.X + i * tp.Rect.Width / length,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
RealocateTextParticles(particlePos);
}
}
private void SliceTextParticleByChar(TextParticle tp, char charToSlice)
{
string[] tokens = tp.Text.Split(charToSlice);
int tokenCount = tokens.Length;
if (tokenCount > 1)
{
int textLength = tp.Text.Length;
for (int i = 0; i < textLength; i++)
{
if (tp.Text[i] == charToSlice)
{
textParticles.Add(CreateTextParticle(
charToSlice.ToString(),
tp.Rect.X,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
}
for (int i = 0; i < tokenCount; i++)
{
int tokenLength = tokens[i].Length;
textParticles.Add(CreateTextParticle(
tokens[i],
tp.Rect.X + i * tp.Rect.Width / tokenLength,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
RealocateTextParticles(0);
}
}
private void ShatterTextParticle(TextParticle tp, int particlePos)
{
SliceTextParticle(tp, particlePos, 1);
}
private void GlueTextParticles(TextParticle grabbed, TextParticle target)
{
int p1 = textParticles.IndexOf(grabbed);
int p2 = textParticles.IndexOf(target);
if ((p1 != -1) && (p2 != -1))
{
TextParticle tp = CreateTextParticle(
grabbed.Text + target.Text,
grabbed.Rect.X,
grabbed.Rect.Y,
Color.RayWhite
);
tp.Grabbed = true;
textParticles.Add(tp);
grabbed.Grabbed = false;
if (p1 < p2)
{
RealocateTextParticles(p2);
RealocateTextParticles(p1);
}
else
{
RealocateTextParticles(p1);
RealocateTextParticles(p2);
}
}
}
private void RealocateTextParticles(int particlePos)
{
textParticles.RemoveAt(particlePos);
}
// Extract a substring, clamping length to the available characters (like raylib TextSubtext)
private static string Subtext(string text, int position, int length)
{
if (position >= text.Length)
{
return "";
}
int maxLength = text.Length - position;
if (length > maxLength)
{
length = maxLength;
}
return text.Substring(position, length);
}
// C# equivalents of raylib TextTo* helpers (behaviour kept identical)
private static string TextToUpper(string text)
{
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
sb.Append((c >= 'a' && c <= 'z') ? (char)(c - 32) : c);
}
return sb.ToString();
}
private static string TextToLower(string text)
{
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
sb.Append((c >= 'A' && c <= 'Z') ? (char)(c + 32) : c);
}
return sb.ToString();
}
private static string TextToPascal(string text)
{
var sb = new StringBuilder(text.Length);
if (text.Length > 0)
{
sb.Append(char.ToUpperInvariant(text[0]));
for (int i = 1; i < text.Length; i++)
{
if (text[i] == '_' && (i + 1) < text.Length)
{
sb.Append(char.ToUpperInvariant(text[i + 1]));
i++;
}
else
{
sb.Append(text[i]);
}
}
}
return sb.ToString();
}
private static string TextToSnake(string text)
{
var sb = new StringBuilder(text.Length);
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c >= 'A' && c <= 'Z')
{
if (i > 0)
{
sb.Append('_');
}
sb.Append((char)(c + 32));
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
private static string TextToCamel(string text)
{
var sb = new StringBuilder(text.Length);
if (text.Length > 0)
{
sb.Append(char.ToLowerInvariant(text[0]));
for (int i = 1; i < text.Length; i++)
{
if (text[i] == '_' && (i + 1) < text.Length)
{
sb.Append(char.ToUpperInvariant(text[i + 1]));
i++;
}
else
{
sb.Append(text[i]);
}
}
}
return sb.ToString();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - strings management");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new StringsManagement();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

739
Examples/Text/Text3D.cs Normal file
View file

@ -0,0 +1,739 @@
/*******************************************************************************************
*
* raylib [text] example - 3d drawing
*
* Example complexity rating: [] 4/4
*
* NOTE: Draw a 2D text in 3D space, each letter is drawn in a quad (or 2 quads if backface is set)
* where the texture coodinates of each quad map to the texture coordinates of the glyphs
* inside the font texture
*
* A more efficient approach, i believe, would be to render the text in a render texture and
* map that texture to a plane and render that, or maybe a shader but my method allows more
* flexibility...for example to change position of each letter individually to make somethink
* like a wavy text effect
*
* Special thanks to:
* @Nighten for the DrawTextStyle() code https://github.com/NightenDushi/Raylib_DrawTextStyle
* Chris Camacho (codifies - http://bedroomcoders.co.uk/) for the alpha discard shader
*
* Example originally created with raylib 3.5, last time updated with raylib 4.0
*
* Example contributed by Vlad Adrian (@demizdor) 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) 2021-2025 Vlad Adrian (@demizdor)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class Text3D : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
//--------------------------------------------------------------------------------------
// Global variables
//--------------------------------------------------------------------------------------
private const float LetterBoundrySize = 0.25f;
private const int TextMaxLayers = 32;
private static readonly Color LetterBoundryColor = Color.Violet;
private bool showLetterBoundry;
private bool showTextBoundry;
//--------------------------------------------------------------------------------------
// Types and Structures Definition
//--------------------------------------------------------------------------------------
// Configuration structure for waving the text
private struct WaveTextConfig
{
public Vector3 WaveRange;
public Vector3 WaveSpeed;
public Vector3 WaveOffset;
}
public string Name => "Text / Text 3D";
public string Title => "raylib [text] example - 3d drawing";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
public bool CursorDisabled => true;
private bool spin; // Spin the camera?
private bool multicolor; // Multicolor mode
private Camera3D camera;
private CameraMode cameraMode;
private Vector3 cubePosition;
private Vector3 cubeSize;
private Font font;
private float fontSize;
private float fontSpacing;
private float lineSpacing;
private string text;
private Vector3 tbox;
private int layers;
private int quads;
private float layerDistance;
private WaveTextConfig wcfg;
private float time;
private Color light;
private Color dark;
private Shader alphaDiscard;
private Color[] multi;
public void Init()
{
spin = true; // Spin the camera?
multicolor = false; // Multicolor mode
showLetterBoundry = false;
showTextBoundry = false;
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
cameraMode = CameraMode.Orbital;
cubePosition = new Vector3(0.0f, 1.0f, 0.0f);
cubeSize = new Vector3(2.0f, 2.0f, 2.0f);
// Use the default font
font = GetFontDefault();
fontSize = 0.8f;
fontSpacing = 0.05f;
lineSpacing = -0.1f;
// Set the text (using markdown!)
text = "Hello ~~World~~ in 3D!";
tbox = new Vector3(0, 0, 0);
layers = 1;
quads = 0;
layerDistance = 0.01f;
wcfg = new WaveTextConfig();
wcfg.WaveSpeed.X = wcfg.WaveSpeed.Y = 3.0f; wcfg.WaveSpeed.Z = 0.5f;
wcfg.WaveOffset.X = wcfg.WaveOffset.Y = wcfg.WaveOffset.Z = 0.35f;
wcfg.WaveRange.X = wcfg.WaveRange.Y = wcfg.WaveRange.Z = 0.45f;
time = 0.0f;
// Setup a light and dark color
light = Color.Maroon;
dark = Color.Red;
// Load the alpha discard shader
alphaDiscard = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/alpha_discard.fs");
// Array filled with multiple random colors (when multicolor mode is set)
multi = new Color[TextMaxLayers];
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, cameraMode);
// Handle font files dropped
if (IsFileDropped())
{
string[] droppedFiles = GetDroppedFiles();
// NOTE: We only support first ttf file dropped
if (IsFileExtension(droppedFiles[0], ".ttf"))
{
UnloadFont(font);
font = LoadFontEx(droppedFiles[0], (int)fontSize, null, 0);
}
else if (IsFileExtension(droppedFiles[0], ".fnt"))
{
UnloadFont(font);
font = LoadFont(droppedFiles[0]);
fontSize = (float)font.BaseSize;
}
}
// Handle Events
if (IsKeyPressed(KeyboardKey.F1)) showLetterBoundry = !showLetterBoundry;
if (IsKeyPressed(KeyboardKey.F2)) showTextBoundry = !showTextBoundry;
if (IsKeyPressed(KeyboardKey.F3))
{
// Handle camera change
spin = !spin;
// we need to reset the camera when changing modes
camera = new();
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera mode type
if (spin)
{
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
cameraMode = CameraMode.Orbital;
}
else
{
camera.Position = new Vector3(10.0f, 10.0f, -10.0f); // Camera position
cameraMode = CameraMode.Free;
}
}
// Handle clicking the cube
if (IsMouseButtonPressed(MouseButton.Left))
{
Ray ray = GetScreenToWorldRay(GetMousePosition(), camera);
// Check collision between ray and box
RayCollision collision = GetRayCollisionBox(ray,
new BoundingBox(
new Vector3(cubePosition.X - cubeSize.X / 2, cubePosition.Y - cubeSize.Y / 2, cubePosition.Z - cubeSize.Z / 2),
new Vector3(cubePosition.X + cubeSize.X / 2, cubePosition.Y + cubeSize.Y / 2, cubePosition.Z + cubeSize.Z / 2)));
if (collision.Hit)
{
// Generate new random colors
light = GenerateRandomColor(0.5f, 0.78f);
dark = GenerateRandomColor(0.4f, 0.58f);
}
}
// Handle text layers changes
if (IsKeyPressed(KeyboardKey.Home)) { if (layers > 1) --layers; }
else if (IsKeyPressed(KeyboardKey.End)) { if (layers < TextMaxLayers) ++layers; }
// Handle text changes
if (IsKeyPressed(KeyboardKey.Left)) fontSize -= 0.5f;
else if (IsKeyPressed(KeyboardKey.Right)) fontSize += 0.5f;
else if (IsKeyPressed(KeyboardKey.Up)) fontSpacing -= 0.1f;
else if (IsKeyPressed(KeyboardKey.Down)) fontSpacing += 0.1f;
else if (IsKeyPressed(KeyboardKey.PageUp)) lineSpacing -= 0.1f;
else if (IsKeyPressed(KeyboardKey.PageDown)) lineSpacing += 0.1f;
else if (IsKeyDown(KeyboardKey.Insert)) layerDistance -= 0.001f;
else if (IsKeyDown(KeyboardKey.Delete)) layerDistance += 0.001f;
else if (IsKeyPressed(KeyboardKey.Tab))
{
multicolor = !multicolor; // Enable /disable multicolor mode
if (multicolor)
{
// Fill color array with random colors
for (int i = 0; i < TextMaxLayers; i++)
{
multi[i] = GenerateRandomColor(0.5f, 0.8f);
multi[i].A = (byte)GetRandomValue(0, 255);
}
}
}
// Handle text input
int ch = GetCharPressed();
if (IsKeyPressed(KeyboardKey.Backspace))
{
// Remove last char
if (text.Length > 0) text = text.Substring(0, text.Length - 1);
}
else if (IsKeyPressed(KeyboardKey.Enter))
{
// handle newline
if (text.Length < 63) text += '\n';
}
else
{
// append only printable chars
if ((ch != 0) && (text.Length < 63)) text += char.ConvertFromUtf32(ch);
}
// Measure 3D text so we can center it
tbox = MeasureTextWave3D(font, text, fontSize, fontSpacing, lineSpacing);
quads = 0; // Reset quad counter
time += GetFrameTime(); // Update timer needed by `DrawTextWave3D()`
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawCubeV(cubePosition, cubeSize, dark);
DrawCubeWires(cubePosition, 2.1f, 2.1f, 2.1f, light);
DrawGrid(10, 2.0f);
// Use a shader to handle the depth buffer issue with transparent textures
// NOTE: more info at https://bedroomcoders.co.uk/posts/198
BeginShaderMode(alphaDiscard);
// Draw the 3D text above the red cube
Rlgl.PushMatrix();
Rlgl.Rotatef(90.0f, 1.0f, 0.0f, 0.0f);
Rlgl.Rotatef(90.0f, 0.0f, 0.0f, -1.0f);
for (int i = 0; i < layers; i++)
{
Color clr = light;
if (multicolor) clr = multi[i];
DrawTextWave3D(font, text, new Vector3(-tbox.X / 2.0f, layerDistance * i, -4.5f), fontSize, fontSpacing, lineSpacing, true, wcfg, time, clr);
}
// Draw the text boundry if set
if (showTextBoundry) DrawCubeWiresV(new Vector3(0.0f, 0.0f, -4.5f + tbox.Z / 2), tbox, dark);
Rlgl.PopMatrix();
// Don't draw the letter boundries for the 3D text below
bool slb = showLetterBoundry;
showLetterBoundry = false;
// Draw 3D options (use default font)
//-------------------------------------------------------------------------
Rlgl.PushMatrix();
Rlgl.Rotatef(180.0f, 0.0f, 1.0f, 0.0f);
string opt = $"< SIZE: {fontSize:0.0} >";
quads += opt.Length;
Vector2 m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
Vector3 pos = new(-m.X / 2.0f, 0.01f, 2.0f);
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< SPACING: {fontSpacing:0.0} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< LINE: {lineSpacing:0.0} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< LBOX: {(slb ? "ON" : "OFF"),3} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
pos.Z += 0.5f + m.Y;
opt = $"< TBOX: {(showTextBoundry ? "ON" : "OFF"),3} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
pos.Z += 0.5f + m.Y;
opt = $"< LAYER DISTANCE: {layerDistance:0.000} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.DarkPurple);
Rlgl.PopMatrix();
//-------------------------------------------------------------------------
// Draw 3D info text (use default font)
//-------------------------------------------------------------------------
opt = "All the text displayed here is in 3D";
quads += 36;
m = MeasureTextEx(GetFontDefault(), opt, 1.0f, 0.05f);
pos = new Vector3(-m.X / 2.0f, 0.01f, 2.0f);
DrawText3D(GetFontDefault(), opt, pos, 1.0f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 1.5f + m.Y;
opt = "press [Left]/[Right] to change the font size";
quads += 44;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [Up]/[Down] to change the font spacing";
quads += 44;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [PgUp]/[PgDown] to change the line spacing";
quads += 48;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [F1] to toggle the letter boundry";
quads += 39;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [F2] to toggle the text boundry";
quads += 37;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
//-------------------------------------------------------------------------
showLetterBoundry = slb;
EndShaderMode();
EndMode3D();
// Draw 2D info text & stats
//-------------------------------------------------------------------------
DrawText("Drag & drop a font file to change the font!\nType something, see what happens!\n\n" +
"Press [F3] to toggle the camera", 10, 35, 10, Color.Black);
quads += TextLengthUtf8(text) * 2 * layers;
string tmp = $"{layers,2} layer(s) | {(spin ? "ORBITAL" : "FREE")} camera | {quads,4} quads ({quads * 4,4} verts)";
int width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 10, 10, Color.DarkGreen);
tmp = "[Home]/[End] to add/remove 3D text layers";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 25, 10, Color.DarkGray);
tmp = "[Insert]/[Delete] to increase/decrease distance between layers";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 40, 10, Color.DarkGray);
tmp = "click the [CUBE] for a random color";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 55, 10, Color.DarkGray);
tmp = "[Tab] to toggle multicolor mode";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 70, 10, Color.DarkGray);
//-------------------------------------------------------------------------
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadFont(font);
}
//--------------------------------------------------------------------------------------
// Module Functions Definitions
//--------------------------------------------------------------------------------------
// Get the total length in bytes of a UTF-8 string (raylib TextLength equivalent)
private static int TextLengthUtf8(string text)
{
return Encoding.UTF8.GetByteCount(text);
}
// Draw codepoint at specified position in 3D space
private unsafe void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint)
{
// Character index position in sprite font
// NOTE: In case a codepoint is not available in the font, index returned points to '?'
int index = GetGlyphIndex(font, codepoint);
float scale = fontSize / (float)font.BaseSize;
// Character destination rectangle on screen
// NOTE: We consider charsPadding on drawing
position.X += (font.Glyphs[index].OffsetX - font.GlyphPadding) * scale;
position.Z += (font.Glyphs[index].OffsetY - font.GlyphPadding) * scale;
// Character source rectangle from font texture atlas
// NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
Rectangle srcRec = new(font.Recs[index].X - font.GlyphPadding, font.Recs[index].Y - font.GlyphPadding,
font.Recs[index].Width + 2.0f * font.GlyphPadding, font.Recs[index].Height + 2.0f * font.GlyphPadding);
float width = (font.Recs[index].Width + 2.0f * font.GlyphPadding) * scale;
float height = (font.Recs[index].Height + 2.0f * font.GlyphPadding) * scale;
if (font.Texture.Id > 0)
{
const float x = 0.0f;
const float y = 0.0f;
const float z = 0.0f;
// normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
float tx = srcRec.X / font.Texture.Width;
float ty = srcRec.Y / font.Texture.Height;
float tw = (srcRec.X + srcRec.Width) / font.Texture.Width;
float th = (srcRec.Y + srcRec.Height) / font.Texture.Height;
if (showLetterBoundry) DrawCubeWiresV(new Vector3(position.X + width / 2, position.Y, position.Z + height / 2), new Vector3(width, LetterBoundrySize, height), LetterBoundryColor);
Rlgl.CheckRenderBatchLimit(4 + 4 * (backface ? 1 : 0));
Rlgl.SetTexture(font.Texture.Id);
Rlgl.PushMatrix();
Rlgl.Translatef(position.X, position.Y, position.Z);
Rlgl.Begin(DrawMode.Quads);
Rlgl.Color4ub(tint.R, tint.G, tint.B, tint.A);
// Front Face
Rlgl.Normal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Left Of The Texture and Quad
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Right Of The Texture and Quad
if (backface)
{
// Back Face
Rlgl.Normal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Right Of The Texture and Quad
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Left Of The Texture and Quad
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad
}
Rlgl.End();
Rlgl.PopMatrix();
Rlgl.SetTexture(0);
}
}
// Draw a 2D text in 3D space
private unsafe void DrawText3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
float textOffsetX = 0.0f; // Offset X to next character to draw
float scale = fontSize / (float)font.BaseSize;
for (int i = 0; i < length;)
{
// Get next codepoint from byte string and glyph index in font
int codepointByteCount = 0;
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
int index = GetGlyphIndex(font, codepoint);
// 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 moving one byte
if (codepoint == 0x3f) codepointByteCount = 1;
if (codepoint == '\n')
{
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += fontSize + lineSpacing;
textOffsetX = 0.0f;
}
else
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint3D(font, codepoint, new Vector3(position.X + textOffsetX, position.Y, position.Z + textOffsetY), fontSize, backface, tint);
}
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
}
i += codepointByteCount; // Move text bytes counter to next codepoint
}
}
// Draw a 2D text in 3D space and wave the parts that start with `~~` and end with `~~`
// This is a modified version of the original code by @Nighten found here https://github.com/NightenDushi/Raylib_DrawTextStyle
private unsafe void DrawTextWave3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, WaveTextConfig config, float time, Color tint)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
float textOffsetX = 0.0f; // Offset X to next character to draw
float scale = fontSize / (float)font.BaseSize;
bool wave = false;
for (int i = 0, k = 0; i < length; ++k)
{
// Get next codepoint from byte string and glyph index in font
int codepointByteCount = 0;
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
int index = GetGlyphIndex(font, codepoint);
// 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 moving one byte
if (codepoint == 0x3f) codepointByteCount = 1;
if (codepoint == '\n')
{
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += fontSize + lineSpacing;
textOffsetX = 0.0f;
k = 0;
}
else if (codepoint == '~')
{
if (GetCodepoint(&t[i + 1], &codepointByteCount) == '~')
{
codepointByteCount += 1;
wave = !wave;
}
}
else
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
Vector3 pos = position;
if (wave) // Apply the wave effect
{
pos.X += MathF.Sin(time * config.WaveSpeed.X - k * config.WaveOffset.X) * config.WaveRange.X;
pos.Y += MathF.Sin(time * config.WaveSpeed.Y - k * config.WaveOffset.Y) * config.WaveRange.Y;
pos.Z += MathF.Sin(time * config.WaveSpeed.Z - k * config.WaveOffset.Z) * config.WaveRange.Z;
}
DrawTextCodepoint3D(font, codepoint, new Vector3(pos.X + textOffsetX, pos.Y, pos.Z + textOffsetY), fontSize, backface, tint);
}
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
}
i += codepointByteCount; // Move text bytes counter to next codepoint
}
}
// Measure a text in 3D ignoring the `~~` chars
private unsafe Vector3 MeasureTextWave3D(Font font, string text, float fontSize, float fontSpacing, float lineSpacing)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int len = Encoding.UTF8.GetByteCount(text);
int tempLen = 0; // Used to count longer text line num chars
int lenCounter = 0;
float tempTextWidth = 0.0f; // Used to count longer text line width
float scale = fontSize / (float)font.BaseSize;
float textHeight = scale;
float textWidth = 0.0f;
int letter = 0; // Current character
int index = 0; // Index position in sprite font
for (int i = 0; i < len; i++)
{
int next = 0;
letter = GetCodepoint(&t[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;
i += next - 1;
if (letter != '\n')
{
if (letter == '~' && GetCodepoint(&t[i + 1], &next) == '~')
{
i++;
}
else
{
lenCounter++;
if (font.Glyphs[index].AdvanceX != 0) textWidth += font.Glyphs[index].AdvanceX * scale;
else textWidth += (font.Recs[index].Width + font.Glyphs[index].OffsetX) * scale;
}
}
else
{
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
lenCounter = 0;
textWidth = 0.0f;
textHeight += fontSize + lineSpacing;
}
if (tempLen < lenCounter) tempLen = lenCounter;
}
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
Vector3 vec = new(0, 0, 0);
vec.X = tempTextWidth + ((tempLen - 1) * fontSpacing); // Adds chars spacing to measure
vec.Y = 0.25f;
vec.Z = textHeight;
return vec;
}
// Generates a nice color with a random hue
private static Color GenerateRandomColor(float s, float v)
{
const float Phi = 0.618033988749895f; // Golden ratio conjugate
float h = (float)GetRandomValue(0, 360);
h = (h + h * Phi) % 360.0f;
return ColorFromHSV(h, s, v);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - 3d drawing");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Text3D();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -22,11 +22,20 @@ using static Raylib_cs.Raylib;
namespace Examples.Text;
public class Unicode
public partial class Unicode : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
const int EmojiPerWidth = 8;
const int EmojiPerHeight = 4;
public string Name => "Text / Unicode Emojis";
public string Title => "raylib [text] example - unicode emojis";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
// Arrays that holds the random emojis
struct EmojiInfo
{
@ -35,10 +44,10 @@ public class Unicode
public Color Color; // Emoji color
}
static EmojiInfo[] emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
private EmojiInfo[] emoji;
static int hovered = -1;
static int selected = -1;
private int hovered;
private int selected;
struct Message
{
@ -130,191 +139,187 @@ public class Unicode
new Message("\xED\x95\x9C\xEA\xB5\xAD\xEB\xA7\x90\x20\xED\x95\x98\xEC\x8B\xA4\x20\xEC\xA4\x84\x20\xEC\x95\x84\xEC\x84\xB8\xEC\x9A\x94\x3F", "Korean"),
};
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
private Font fontDefault;
private Font fontAsian;
private Font fontEmoji;
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
private Vector2 hoveredPos;
private Vector2 selectedPos;
public void Init()
{
emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
hovered = -1;
selected = -1;
// Load the font resources
// NOTE: fontAsian is for asian languages,
// fontEmoji is the emojis and fontDefault is used for everything else
Font fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
Font fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
Font fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
Vector2 hoveredPos = new(0.0f, 0.0f);
Vector2 selectedPos = new(0.0f, 0.0f);
hoveredPos = new(0.0f, 0.0f);
selectedPos = new(0.0f, 0.0f);
// Set a random set of emojis when starting up
RandomizeEmoji();
}
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Add a new set of emojis when SPACE is pressed
if (IsKeyPressed(KeyboardKey.Space))
{
// Update
//----------------------------------------------------------------------------------
// Add a new set of emojis when SPACE is pressed
if (IsKeyPressed(KeyboardKey.Space))
{
RandomizeEmoji();
}
// Set the selected emoji
if (IsMouseButtonPressed(MouseButton.Left) && (hovered != -1) && (hovered != selected))
{
selected = hovered;
selectedPos = hoveredPos;
}
Vector2 mouse = GetMousePosition();
Vector2 position = new(28.8f, 10.0f);
hovered = -1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw random emojis in the background
//------------------------------------------------------------------------------
for (int i = 0; i < emoji.Length; i++)
{
string txt = GetEmojiAt(emoji[i].Index);
Rectangle emojiRect = new(position.X, position.Y, fontEmoji.BaseSize, fontEmoji.BaseSize);
if (!CheckCollisionPointRec(mouse, emojiRect))
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, selected == i ? emoji[i].Color : Fade(Color.LightGray, 0.4f));
}
else
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, emoji[i].Color);
hovered = i;
hoveredPos = position;
}
if ((i != 0) && (i % EmojiPerWidth == 0))
{
position.Y += fontEmoji.BaseSize + 24.25f;
position.X = 28.8f;
}
else
{
position.X += fontEmoji.BaseSize + 28.8f;
}
}
//------------------------------------------------------------------------------
// Draw the message when a emoji is selected
//------------------------------------------------------------------------------
if (selected != -1)
{
int message = emoji[selected].Message;
const int horizontalPadding = 20;
const int verticalPadding = 30;
Font font = fontDefault;
// Set correct font for asian languages
if ((messages[message].Language == "Chinese") ||
(messages[message].Language == "Korean") ||
(messages[message].Language == "Japanese"))
{
font = fontAsian;
}
// Calculate size for the message box (approximate the height and width)
Vector2 sz = MeasureTextEx(font, messages[message].Text, font.BaseSize, 1.0f);
if (sz.X > 300)
{
sz.Y *= sz.X / 300;
sz.X = 300;
}
else if (sz.X < 160)
{
sz.X = 160;
}
Rectangle msgRect = new(selectedPos.X - 38.8f, selectedPos.Y, 2 * horizontalPadding + sz.X, 2 * verticalPadding + sz.Y);
msgRect.Y -= msgRect.Height;
// Coordinates for the chat bubble triangle
Vector2 a = new(selectedPos.X, msgRect.Y + msgRect.Height);
Vector2 b = new(a.X + 8, a.Y + 10);
Vector2 c = new(a.X + 10, a.Y);
// Don't go outside the screen
if (msgRect.X < 10)
{
msgRect.X += 28;
}
if (msgRect.Y < 10)
{
msgRect.Y = selectedPos.Y + 84;
a.Y = msgRect.Y;
c.Y = a.Y;
b.Y = a.Y - 10;
// Swap values so we can actually render the triangle :(
Vector2 tmp = a;
a = b;
b = tmp;
}
if (msgRect.X + msgRect.Width > screenWidth)
{
msgRect.X -= (msgRect.X + msgRect.Width) - screenWidth + 10;
}
// Draw chat bubble
DrawRectangleRec(msgRect, emoji[selected].Color);
DrawTriangle(a, b, c, emoji[selected].Color);
// Draw the main text message
Rectangle textRect = new(msgRect.X + (float)horizontalPadding / 2, msgRect.Y + (float)verticalPadding / 2, msgRect.Width - horizontalPadding, msgRect.Height);
DrawTextBoxed(font, messages[message].Text, textRect, font.BaseSize, 1.0f, true, Color.White);
// Draw the info text below the main message
int size = Encoding.UTF8.GetByteCount(messages[message].Text);
int length = GetCodepointCount(messages[message].Text);
string info = $"{messages[message].Language} {length} characters {size} bytes";
sz = MeasureTextEx(GetFontDefault(), info, 10, 1.0f);
DrawText(info, (int)(textRect.X + textRect.Width - sz.X), (int)(msgRect.Y + msgRect.Height - sz.Y - 2), 10, Color.RayWhite);
}
//------------------------------------------------------------------------------
// Draw the info text
DrawText("These emojis have something to tell you, click each to find out!", (screenWidth - 650) / 2, screenHeight - 40, 20, Color.Gray);
DrawText("Each emoji is a unicode character from a font, not a texture... Press [SPACEBAR] to refresh", (screenWidth - 484) / 2, screenHeight - 16, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
RandomizeEmoji();
}
// De-Initialization
//--------------------------------------------------------------------------------------
// Set the selected emoji
if (IsMouseButtonPressed(MouseButton.Left) && (hovered != -1) && (hovered != selected))
{
selected = hovered;
selectedPos = hoveredPos;
}
Vector2 mouse = GetMousePosition();
Vector2 position = new(28.8f, 10.0f);
hovered = -1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw random emojis in the background
//------------------------------------------------------------------------------
for (int i = 0; i < emoji.Length; i++)
{
string txt = GetEmojiAt(emoji[i].Index);
Rectangle emojiRect = new(position.X, position.Y, fontEmoji.BaseSize, fontEmoji.BaseSize);
if (!CheckCollisionPointRec(mouse, emojiRect))
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, selected == i ? emoji[i].Color : Fade(Color.LightGray, 0.4f));
}
else
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, emoji[i].Color);
hovered = i;
hoveredPos = position;
}
if ((i != 0) && (i % EmojiPerWidth == 0))
{
position.Y += fontEmoji.BaseSize + 24.25f;
position.X = 28.8f;
}
else
{
position.X += fontEmoji.BaseSize + 28.8f;
}
}
//------------------------------------------------------------------------------
// Draw the message when a emoji is selected
//------------------------------------------------------------------------------
if (selected != -1)
{
int message = emoji[selected].Message;
const int horizontalPadding = 20;
const int verticalPadding = 30;
Font font = fontDefault;
// Set correct font for asian languages
if ((messages[message].Language == "Chinese") ||
(messages[message].Language == "Korean") ||
(messages[message].Language == "Japanese"))
{
font = fontAsian;
}
// Calculate size for the message box (approximate the height and width)
Vector2 sz = MeasureTextEx(font, messages[message].Text, font.BaseSize, 1.0f);
if (sz.X > 300)
{
sz.Y *= sz.X / 300;
sz.X = 300;
}
else if (sz.X < 160)
{
sz.X = 160;
}
Rectangle msgRect = new(selectedPos.X - 38.8f, selectedPos.Y, 2 * horizontalPadding + sz.X, 2 * verticalPadding + sz.Y);
msgRect.Y -= msgRect.Height;
// Coordinates for the chat bubble triangle
Vector2 a = new(selectedPos.X, msgRect.Y + msgRect.Height);
Vector2 b = new(a.X + 8, a.Y + 10);
Vector2 c = new(a.X + 10, a.Y);
// Don't go outside the screen
if (msgRect.X < 10)
{
msgRect.X += 28;
}
if (msgRect.Y < 10)
{
msgRect.Y = selectedPos.Y + 84;
a.Y = msgRect.Y;
c.Y = a.Y;
b.Y = a.Y - 10;
// Swap values so we can actually render the triangle :(
Vector2 tmp = a;
a = b;
b = tmp;
}
if (msgRect.X + msgRect.Width > screenWidth)
{
msgRect.X -= (msgRect.X + msgRect.Width) - screenWidth + 10;
}
// Draw chat bubble
DrawRectangleRec(msgRect, emoji[selected].Color);
DrawTriangle(a, b, c, emoji[selected].Color);
// Draw the main text message
Rectangle textRect = new(msgRect.X + (float)horizontalPadding / 2, msgRect.Y + (float)verticalPadding / 2, msgRect.Width - horizontalPadding, msgRect.Height);
DrawTextBoxed(font, messages[message].Text, textRect, font.BaseSize, 1.0f, true, Color.White);
// Draw the info text below the main message
int size = Encoding.UTF8.GetByteCount(messages[message].Text);
int length = GetCodepointCount(messages[message].Text);
string info = $"{messages[message].Language} {length} characters {size} bytes";
sz = MeasureTextEx(GetFontDefault(), info, 10, 1.0f);
DrawText(info, (int)(textRect.X + textRect.Width - sz.X), (int)(msgRect.Y + msgRect.Height - sz.Y - 2), 10, Color.RayWhite);
}
//------------------------------------------------------------------------------
// Draw the info text
DrawText("These emojis have something to tell you, click each to find out!", (screenWidth - 650) / 2, screenHeight - 40, 20, Color.Gray);
DrawText("Each emoji is a unicode character from a font, not a texture... Press [SPACEBAR] to refresh", (screenWidth - 484) / 2, screenHeight - 16, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadFont(fontDefault); // Unload font resource
UnloadFont(fontAsian); // Unload font resource
UnloadFont(fontEmoji); // Unload font resource
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
// Fills the emoji array with random emoji (only those emojis present in fontEmoji)
static void RandomizeEmoji()
void RandomizeEmoji()
{
hovered = selected = -1;
int start = GetRandomValue(45, 360);
@ -510,4 +515,33 @@ public class Unicode
textOffsetX += glyphWidth;
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Unicode();
game.Init();
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,225 @@
/*******************************************************************************************
*
* raylib [text] example - unicode ranges
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Vadim Gunko (@GuvaCode) 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 Vadim Gunko (@GuvaCode) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class UnicodeRanges : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// path differs from upstream: font lives under resources/fonts/
private const string FontPath = "resources/fonts/NotoSansTC-Regular.ttf";
public string Name => "Text / Unicode Ranges";
public string Title => "raylib [text] example - unicode ranges";
private Font font;
private int unicodeRange; // Track the ranges of codepoints added to font
private int prevUnicodeRange; // Previous Unicode range to avoid reloading every frame
public void Init()
{
// Load font with default Unicode range: Basic ASCII [32-127]
font = LoadFont(FontPath);
SetTextureFilter(font.Texture, TextureFilter.Bilinear);
unicodeRange = 0;
prevUnicodeRange = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (unicodeRange != prevUnicodeRange)
{
UnloadFont(font);
// Load font with default Unicode range: Basic ASCII [32-127]
font = LoadFont(FontPath);
// Add required ranges to loaded font
// NOTE: The upstream switch uses fall-through so range N also loads all lower
// ranges, in the same order (4 -> 3 -> 2 -> 1); the descending if-chain preserves that.
/*
if (unicodeRange >= 5)
{
// Unicode range: Devanari, Arabic, Hebrew
// WARNING: Glyphs not available on provided font!
AddCodepointRange(ref font, FontPath, 0x900, 0x97f); // Devanagari
AddCodepointRange(ref font, FontPath, 0x600, 0x6ff); // Arabic
AddCodepointRange(ref font, FontPath, 0x5d0, 0x5ea); // Hebrew
}
*/
if (unicodeRange >= 4)
{
// Unicode range: CJK (Japanese and Chinese)
// WARNING: Loading thousands of codepoints requires lot of time!
// A better strategy is prefilter the required codepoints for the text
// in the game and just load the required ones
AddCodepointRange(ref font, FontPath, 0x4e00, 0x9fff);
AddCodepointRange(ref font, FontPath, 0x3400, 0x4dbf);
AddCodepointRange(ref font, FontPath, 0x3000, 0x303f);
AddCodepointRange(ref font, FontPath, 0x3040, 0x309f);
AddCodepointRange(ref font, FontPath, 0x30A0, 0x30ff);
AddCodepointRange(ref font, FontPath, 0x31f0, 0x31ff);
AddCodepointRange(ref font, FontPath, 0xff00, 0xffef);
AddCodepointRange(ref font, FontPath, 0xac00, 0xd7af);
AddCodepointRange(ref font, FontPath, 0x1100, 0x11ff);
}
if (unicodeRange >= 3)
{
// Unicode range: Cyrillic
AddCodepointRange(ref font, FontPath, 0x400, 0x4ff);
AddCodepointRange(ref font, FontPath, 0x500, 0x52f);
AddCodepointRange(ref font, FontPath, 0x2de0, 0x2Dff);
AddCodepointRange(ref font, FontPath, 0xa640, 0xA69f);
}
if (unicodeRange >= 2)
{
// Unicode range: Greek
AddCodepointRange(ref font, FontPath, 0x370, 0x3ff);
AddCodepointRange(ref font, FontPath, 0x1f00, 0x1fff);
}
if (unicodeRange >= 1)
{
// Unicode range: European Languages
AddCodepointRange(ref font, FontPath, 0xc0, 0x17f);
AddCodepointRange(ref font, FontPath, 0x180, 0x24f);
//AddCodepointRange(ref font, FontPath, 0x1e00, 0x1eff);
//AddCodepointRange(ref font, FontPath, 0x2c60, 0x2c7f);
}
prevUnicodeRange = unicodeRange;
SetTextureFilter(font.Texture, TextureFilter.Bilinear); // Set font atlas scale filter
}
if (IsKeyPressed(KeyboardKey.Zero)) unicodeRange = 0;
else if (IsKeyPressed(KeyboardKey.One)) unicodeRange = 1;
else if (IsKeyPressed(KeyboardKey.Two)) unicodeRange = 2;
else if (IsKeyPressed(KeyboardKey.Three)) unicodeRange = 3;
else if (IsKeyPressed(KeyboardKey.Four)) unicodeRange = 4;
//else if (IsKeyPressed(KeyboardKey.Five)) unicodeRange = 5;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("ADD CODEPOINTS: [1][2][3][4]", 20, 20, 20, Color.Maroon);
// Render test strings in different languages
DrawTextEx(font, "> English: Hello World!", new Vector2(50, 70), 32, 1, Color.DarkGray); // English
DrawTextEx(font, "> Español: Hola mundo!", new Vector2(50, 120), 32, 1, Color.DarkGray); // Spanish
DrawTextEx(font, "> Ελληνικά: Γειά σου κόσμε!", new Vector2(50, 170), 32, 1, Color.DarkGray); // Greek
DrawTextEx(font, "> Русский: Привет мир!", new Vector2(50, 220), 32, 0, Color.DarkGray); // Russian
DrawTextEx(font, "> 中文: 你好世界!", new Vector2(50, 270), 32, 1, Color.DarkGray); // Chinese
DrawTextEx(font, "> 日本語: こんにちは世界!", new Vector2(50, 320), 32, 1, Color.DarkGray); // Japanese
//DrawTextEx(font, "देवनागरी: होला मुंडो!", new Vector2(50, 350), 32, 1, Color.DarkGray); // Devanagari (glyphs not available in font)
// Draw font texture scaled to screen
float atlasScale = 380.0f / font.Texture.Width;
DrawRectangleRec(new Rectangle(400.0f, 16.0f, font.Texture.Width * atlasScale, font.Texture.Height * atlasScale), Color.Black);
DrawTexturePro(font.Texture, new Rectangle(0, 0, font.Texture.Width, font.Texture.Height),
new Rectangle(400.0f, 16.0f, font.Texture.Width * atlasScale, font.Texture.Height * atlasScale), new Vector2(0, 0), 0.0f, Color.White);
DrawRectangleLines(400, 16, 380, 380, Color.Red);
DrawText($"ATLAS SIZE: {font.Texture.Width}x{font.Texture.Height} px (x{atlasScale:00.00})", 20, 380, 20, Color.Blue);
DrawText($"CODEPOINTS GLYPHS LOADED: {font.GlyphCount}", 20, 410, 20, Color.Lime);
// Display font attribution
DrawText("Font: Noto Sans TC. License: SIL Open Font License 1.1", screenWidth - 300, screenHeight - 20, 10, Color.Gray);
if (prevUnicodeRange != unicodeRange)
{
DrawRectangle(0, 0, screenWidth, screenHeight, Fade(Color.White, 0.8f));
DrawRectangle(0, 125, screenWidth, 200, Color.Gray);
DrawText("GENERATING FONT ATLAS...", 120, 210, 40, Color.Black);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadFont(font); // Unload font resource
}
//--------------------------------------------------------------------------------------
// Module Functions Definition
//--------------------------------------------------------------------------------------
// Add codepoint range to existing font
private static unsafe void AddCodepointRange(ref Font font, string fontPath, int start, int stop)
{
int rangeSize = stop - start + 1;
int currentRangeSize = font.GlyphCount;
// TODO: Load glyphs from provided vector font (if available),
// add them to existing font, regenerating font image and texture
int updatedCodepointCount = currentRangeSize + rangeSize;
int[] updatedCodepoints = new int[updatedCodepointCount];
// Get current codepoint list
for (int i = 0; i < currentRangeSize; i++) updatedCodepoints[i] = font.Glyphs[i].Value;
// Add new codepoints to list (provided range)
for (int i = currentRangeSize; i < updatedCodepointCount; i++)
updatedCodepoints[i] = start + (i - currentRangeSize);
UnloadFont(font);
font = LoadFontEx(fontPath, 32, updatedCodepoints, updatedCodepointCount);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode ranges");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new UnicodeRanges();
game.Init();
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,167 @@
/*******************************************************************************************
*
* raylib [text] example - words alignment
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by JP Mortiboys (@themushroompirates) 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 JP Mortiboys (@themushroompirates)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath; // Required for: Lerp()
namespace Examples.Text;
public partial class WordsAlignment : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// TextAlignment values: Left/Top = 0, Centre/Middle = 1, Right/Bottom = 2
public string Name => "Text / Words Alignment";
public string Title => "raylib [text] example - words alignment";
// Define the rectangle we will draw the text in
private Rectangle textContainerRect;
// Some text to display the current alignment
private static readonly string[] textAlignNameH = { "Left", "Centre", "Right" };
private static readonly string[] textAlignNameV = { "Top", "Middle", "Bottom" };
// Define the text we're going to draw in the rectangle
private int wordIndex;
private int wordCount;
private string[] words;
// Initialize the font size we're going to use
private int fontSize;
// And of course the font...
private Font font;
// Initialize the alignment variables
private int hAlign;
private int vAlign;
public void Init()
{
// Define the rectangle we will draw the text in
textContainerRect = new Rectangle((float)screenWidth / 2 - (float)screenWidth / 4, (float)screenHeight / 2 - (float)screenHeight / 3, (float)screenWidth / 2, (float)screenHeight * 2 / 3);
// Define the text we're going to draw in the rectangle
wordIndex = 0;
words = "raylib is a simple and easy-to-use library to enjoy videogames programming".Split(' ');
wordCount = words.Length;
// Initialize the font size we're going to use
fontSize = 40;
// And of course the font...
font = GetFontDefault();
// Initialize the alignment variables
hAlign = 1; // TEXT_ALIGN_CENTRE
vAlign = 1; // TEXT_ALIGN_MIDDLE
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Left))
{
if (hAlign > 0) hAlign = hAlign - 1;
}
if (IsKeyPressed(KeyboardKey.Right))
{
hAlign = hAlign + 1;
if (hAlign > 2) hAlign = 2;
}
if (IsKeyPressed(KeyboardKey.Up))
{
if (vAlign > 0) vAlign = vAlign - 1;
}
if (IsKeyPressed(KeyboardKey.Down))
{
vAlign = vAlign + 1;
if (vAlign > 2) vAlign = 2;
}
// One word per second
if (wordCount > 0) wordIndex = (int)GetTime() % wordCount;
else wordIndex = 0;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.DarkBlue);
DrawText("Use Arrow Keys to change the text alignment", 20, 20, 20, Color.LightGray);
DrawText($"Alignment: Horizontal = {textAlignNameH[hAlign]}, Vertical = {textAlignNameV[vAlign]}", 20, 40, 20, Color.LightGray);
DrawRectangleRec(textContainerRect, Color.Blue);
// Get the size of the text to draw
Vector2 textSize = MeasureTextEx(font, words[wordIndex], fontSize, fontSize * .1f);
// Calculate the top-left text position based on the rectangle and alignment
Vector2 textPos = new Vector2(
textContainerRect.X + Lerp(0.0f, textContainerRect.Width - textSize.X, hAlign * 0.5f),
textContainerRect.Y + Lerp(0.0f, textContainerRect.Height - textSize.Y, vAlign * 0.5f)
);
// Draw the text
DrawTextEx(font, words[wordIndex], textPos, fontSize, fontSize * .1f, Color.RayWhite);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - words alignment");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new WordsAlignment();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}