unicode ranges
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 - unicode ranges
//!
//! Zig-API translation of examples/text/text_unicode_ranges.c (raylib 6.2).
//! Example contributed by Vadim Gunko (@GuvaCode) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2025 Vadim Gunko (@GuvaCode) and Ramon Santamaria (@raysan5)
const std = @import("std");
const rl = @import("raylib");
const font_path = "resources/NotoSansTC-Regular.ttf";
/// An inclusive range of codepoints
const Range = struct { start: i32, stop: i32 };
/// Codepoint ranges added for each level; a level also includes all the ranges below it.
const level_ranges = [_][]const Range{
// 0: Basic ASCII [32-127] (font default)
&.{},
// 1: European Languages
&.{
.{ .start = 0xc0, .stop = 0x17f },
.{ .start = 0x180, .stop = 0x24f },
//.{ .start = 0x1e00, .stop = 0x1eff },
//.{ .start = 0x2c60, .stop = 0x2c7f },
},
// 2: Greek
&.{
.{ .start = 0x370, .stop = 0x3ff },
.{ .start = 0x1f00, .stop = 0x1fff },
},
// 3: Cyrillic
&.{
.{ .start = 0x400, .stop = 0x4ff },
.{ .start = 0x500, .stop = 0x52f },
.{ .start = 0x2de0, .stop = 0x2Dff },
.{ .start = 0xa640, .stop = 0xA69f },
},
// 4: 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
&.{
.{ .start = 0x4e00, .stop = 0x9fff },
.{ .start = 0x3400, .stop = 0x4dbf },
.{ .start = 0x3000, .stop = 0x303f },
.{ .start = 0x3040, .stop = 0x309f },
.{ .start = 0x30A0, .stop = 0x30ff },
.{ .start = 0x31f0, .stop = 0x31ff },
.{ .start = 0xff00, .stop = 0xffef },
.{ .start = 0xac00, .stop = 0xd7af },
.{ .start = 0x1100, .stop = 0x11ff },
},
// 5: Devanari, Arabic, Hebrew
// WARNING: Glyphs not available on provided font!
//&.{
// .{ .start = 0x900, .stop = 0x97f }, // Devanagari
// .{ .start = 0x600, .stop = 0x6ff }, // Arabic
// .{ .start = 0x5d0, .stop = 0x5ea }, // Hebrew
//},
};
pub fn main() !void {
const allocator = std.heap.c_allocator;
// Initialization
const screen_width = 800;
const screen_height = 450;
rl.initWindow(screen_width, screen_height, "raylib [text] example - unicode ranges");
defer rl.closeWindow(); // Close window and OpenGL context
// Load font with default Unicode range: Basic ASCII [32-127]
var font = try rl.loadFont(font_path);
defer rl.unloadFont(font); // Unload font resource
rl.setTextureFilter(font.texture, .bilinear);
var unicode_range: usize = 0; // Track the ranges of codepoints added to font
var prev_unicode_range: usize = 0; // Previous Unicode range to avoid reloading every frame
rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second
// Main loop
while (!rl.windowShouldClose()) { // Detect window close button or ESC key
// Update
if (unicode_range != prev_unicode_range) {
// Reload font with default Unicode range: Basic ASCII [32-127]
const reloaded = try rl.loadFont(font_path);
rl.unloadFont(font);
font = reloaded;
// Add required ranges to loaded font, highest level first
var level = unicode_range + 1;
while (level > 0) {
level -= 1;
for (level_ranges[level]) |range| try addCodepointRange(allocator, &font, font_path, range);
}
prev_unicode_range = unicode_range;
rl.setTextureFilter(font.texture, .bilinear); // Set font atlas scale filter
}
if (rl.isKeyPressed(.zero)) {
unicode_range = 0;
} else if (rl.isKeyPressed(.one)) {
unicode_range = 1;
} else if (rl.isKeyPressed(.two)) {
unicode_range = 2;
} else if (rl.isKeyPressed(.three)) {
unicode_range = 3;
} else if (rl.isKeyPressed(.four)) {
unicode_range = 4;
}
//else if (rl.isKeyPressed(.five)) unicode_range = 5;
// Draw
rl.beginDrawing();
defer rl.endDrawing();
rl.clearBackground(.ray_white);
rl.drawText("ADD CODEPOINTS: [1][2][3][4]", 20, 20, 20, .maroon);
// Render test strings in different languages
rl.drawTextEx(font, "> English: Hello World!", .{ .x = 50, .y = 70 }, 32, 1, .dark_gray); // English
rl.drawTextEx(font, "> Español: Hola mundo!", .{ .x = 50, .y = 120 }, 32, 1, .dark_gray); // Spanish
rl.drawTextEx(font, "> Ελληνικά: Γειά σου κόσμε!", .{ .x = 50, .y = 170 }, 32, 1, .dark_gray); // Greek
rl.drawTextEx(font, "> Русский: Привет мир!", .{ .x = 50, .y = 220 }, 32, 0, .dark_gray); // Russian
rl.drawTextEx(font, "> 中文: 你好世界!", .{ .x = 50, .y = 270 }, 32, 1, .dark_gray); // Chinese
rl.drawTextEx(font, "> 日本語: こんにちは世界!", .{ .x = 50, .y = 320 }, 32, 1, .dark_gray); // Japanese
//rl.drawTextEx(font, "देवनागरी: होला मुंडो!", .{ .x = 50, .y = 350 }, 32, 1, .dark_gray); // Devanagari (glyphs not available in font)
// Draw font texture scaled to screen
const atlas_width: f32 = @floatFromInt(font.texture.width);
const atlas_height: f32 = @floatFromInt(font.texture.height);
const atlas_scale = 380.0 / atlas_width;
const atlas_rec: rl.Rectangle = .{ .x = 400.0, .y = 16.0, .width = atlas_width * atlas_scale, .height = atlas_height * atlas_scale };
rl.drawRectangleRec(atlas_rec, .black);
rl.drawTexturePro(font.texture, .{ .x = 0, .y = 0, .width = atlas_width, .height = atlas_height }, atlas_rec, .{ .x = 0, .y = 0 }, 0.0, .white);
rl.drawRectangleLines(400, 16, 380, 380, .red);
rl.drawText(rl.textFormat("ATLAS SIZE: {d}x{d} px (x{d:.2})", .{ font.texture.width, font.texture.height, atlas_scale }), 20, 380, 20, .blue);
rl.drawText(rl.textFormat("CODEPOINTS GLYPHS LOADED: {d}", .{font.glyphCount}), 20, 410, 20, .lime);
// Display font attribution
rl.drawText("Font: Noto Sans TC. License: SIL Open Font License 1.1", screen_width - 300, screen_height - 20, 10, .gray);
if (prev_unicode_range != unicode_range) {
rl.drawRectangle(0, 0, screen_width, screen_height, rl.fade(.white, 0.8));
rl.drawRectangle(0, 125, screen_width, 200, .gray);
rl.drawText("GENERATING FONT ATLAS...", 120, 210, 40, .black);
}
}
}
/// Add codepoint range to existing font, reloading it from `path` with the
/// current glyphs plus the provided range (regenerating font image and texture)
fn addCodepointRange(allocator: std.mem.Allocator, font: *rl.Font, path: [:0]const u8, range: Range) !void {
const current_glyphs = font.glyphSlice();
const range_size: usize = @intCast(range.stop - range.start + 1);
const updated_codepoints = try allocator.alloc(i32, current_glyphs.len + range_size);
defer allocator.free(updated_codepoints);
// Get current codepoint list
for (updated_codepoints[0..current_glyphs.len], current_glyphs) |*codepoint, glyph| codepoint.* = glyph.value;
// Add new codepoints to list (provided range)
for (updated_codepoints[current_glyphs.len..], 0..) |*codepoint, offset| codepoint.* = range.start + @as(i32, @intCast(offset));
const updated = try rl.loadFontEx(path, 32, updated_codepoints);
rl.unloadFont(font.*);
font.* = updated;
}/*******************************************************************************************
*
* 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)
*
********************************************************************************************/
#include "raylib.h"
#include <stdlib.h> // Required for: calloc(), free()
//--------------------------------------------------------------------------------------
// Module Functions Declaration
//--------------------------------------------------------------------------------------
// Add codepoint range to existing font
static void AddCodepointRange(Font *font, const char *fontPath, int start, int stop);
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode ranges");
// Load font with default Unicode range: Basic ASCII [32-127]
Font font = LoadFont("resources/NotoSansTC-Regular.ttf");
SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR);
int unicodeRange = 0; // Track the ranges of codepoints added to font
int prevUnicodeRange = 0; // Previous Unicode range to avoid reloading every frame
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
if (unicodeRange != prevUnicodeRange)
{
UnloadFont(font);
// Load font with default Unicode range: Basic ASCII [32-127]
font = LoadFont("resources/NotoSansTC-Regular.ttf");
// Add required ranges to loaded font
switch (unicodeRange)
{
/*
case 5:
{
// Unicode range: Devanari, Arabic, Hebrew
// WARNING: Glyphs not available on provided font!
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x900, 0x97f); // Devanagari
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x600, 0x6ff); // Arabic
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x5d0, 0x5ea); // Hebrew
}
*/
case 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(&font, "resources/NotoSansTC-Regular.ttf", 0x4e00, 0x9fff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x3400, 0x4dbf);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x3000, 0x303f);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x3040, 0x309f);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x30A0, 0x30ff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x31f0, 0x31ff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0xff00, 0xffef);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0xac00, 0xd7af);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x1100, 0x11ff);
}
case 3:
{
// Unicode range: Cyrillic
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x400, 0x4ff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x500, 0x52f);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x2de0, 0x2Dff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0xa640, 0xA69f);
}
case 2:
{
// Unicode range: Greek
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x370, 0x3ff);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x1f00, 0x1fff);
}
case 1:
{
// Unicode range: European Languages
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0xc0, 0x17f);
AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x180, 0x24f);
//AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x1e00, 0x1eff);
//AddCodepointRange(&font, "resources/NotoSansTC-Regular.ttf", 0x2c60, 0x2c7f);
}
default: break;
}
prevUnicodeRange = unicodeRange;
SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); // Set font atlas scale filter
}
if (IsKeyPressed(KEY_ZERO)) unicodeRange = 0;
else if (IsKeyPressed(KEY_ONE)) unicodeRange = 1;
else if (IsKeyPressed(KEY_TWO)) unicodeRange = 2;
else if (IsKeyPressed(KEY_THREE)) unicodeRange = 3;
else if (IsKeyPressed(KEY_FOUR)) unicodeRange = 4;
//else if (IsKeyPressed(KEY_FIVE)) unicodeRange = 5;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
DrawText("ADD CODEPOINTS: [1][2][3][4]", 20, 20, 20, MAROON);
// Render test strings in different languages
DrawTextEx(font, "> English: Hello World!", (Vector2){ 50, 70 }, 32, 1, DARKGRAY); // English
DrawTextEx(font, "> Español: Hola mundo!", (Vector2){ 50, 120 }, 32, 1, DARKGRAY); // Spanish
DrawTextEx(font, "> Ελληνικά: Γειά σου κόσμε!", (Vector2){ 50, 170 }, 32, 1, DARKGRAY); // Greek
DrawTextEx(font, "> Русский: Привет мир!", (Vector2){ 50, 220 }, 32, 0, DARKGRAY); // Russian
DrawTextEx(font, "> 中文: 你好世界!", (Vector2){ 50, 270 }, 32, 1, DARKGRAY); // Chinese
DrawTextEx(font, "> 日本語: こんにちは世界!", (Vector2){ 50, 320 }, 32, 1, DARKGRAY); // Japanese
//DrawTextEx(font, "देवनागरी: होला मुंडो!", (Vector2){ 50, 350 }, 32, 1, DARKGRAY); // Devanagari (glyphs not available in font)
// Draw font texture scaled to screen
float atlasScale = 380.0f/font.texture.width;
DrawRectangleRec((Rectangle) { 400.0f, 16.0f, font.texture.width* atlasScale, font.texture.height* atlasScale }, BLACK);
DrawTexturePro(font.texture, (Rectangle){ 0, 0, (float)font.texture.width, (float)font.texture.height },
(Rectangle){ 400.0f, 16.0f, font.texture.width*atlasScale, font.texture.height*atlasScale }, (Vector2){ 0, 0 }, 0.0f, WHITE);
DrawRectangleLines(400, 16, 380, 380, RED);
DrawText(TextFormat("ATLAS SIZE: %ix%i px (x%02.2f)", font.texture.width, font.texture.height, atlasScale), 20, 380, 20, BLUE);
DrawText(TextFormat("CODEPOINTS GLYPHS LOADED: %i", font.glyphCount), 20, 410, 20, LIME);
// Display font attribution
DrawText("Font: Noto Sans TC. License: SIL Open Font License 1.1", screenWidth - 300, screenHeight - 20, 10, GRAY);
if (prevUnicodeRange != unicodeRange)
{
DrawRectangle(0, 0, screenWidth, screenHeight, Fade(WHITE, 0.8f));
DrawRectangle(0, 125, screenWidth, 200, GRAY);
DrawText("GENERATING FONT ATLAS...", 120, 210, 40, BLACK);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadFont(font); // Unload font resource
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
//--------------------------------------------------------------------------------------
// Module Functions Definition
//--------------------------------------------------------------------------------------
// Add codepoint range to existing font
static void AddCodepointRange(Font *font, const char *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 = (int *)RL_CALLOC(updatedCodepointCount, sizeof(int));
// 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);
RL_FREE(updatedCodepoints);
}
From raylib 6.2. The original on GitHub.