const rl = @import("raylib"); const std = @import("std"); const UI = @This(); font: rl.Font, pub fn init() UI { return UI{ .font = rl.getFontDefault() }; } pub fn deinit(self: UI) void { rl.unloadFont(self.font); } // Reimplementation of `GetGlyphIndex` from raylib in src/rtext.c fn GetGlyphIndex(font: rl.Font, codepoint: i32) usize { var index: usize = 0; var fallbackIndex: usize = 0; // Get index of fallback glyph '?' for (0..@intCast(font.glyphCount), font.glyphs) |i, glyph| { if (glyph.value == '?') fallbackIndex = i; if (glyph.value == codepoint) { index = i; break; } } if ((index == 0) and (font.glyphs[0].value != codepoint)) index = fallbackIndex; return index; } fn GetCodePointNext(text: []const u8, next: *usize) i32 { var letter: i32 = '?'; if (std.unicode.utf8ByteSequenceLength(text[0])) |codepointSize| { next.* = codepointSize; if (std.unicode.utf8Decode(text[0..codepointSize])) |codepoint| { letter = @intCast(codepoint); } else |_| {} } else |_| {} return letter; } // NOTE: Line spacing is a global variable, use SetTextLineSpacing() to setup const textLineSpacing = 2; // TODO: Assume that line spacing is not changed. // Reimplementation of `rl.drawTextEx`, so a null terminated would not be required pub fn drawTextEx(font: rl.Font, text: []const u8, position: rl.Vector2, font_size: f32, spacing: f32, tint: rl.Color) void { var used_font = font; if (font.texture.id == 0) { used_font = rl.getFontDefault(); } var text_offset_y: f32 = 0; var text_offset_x: f32 = 0; const scale_factor = font_size / @as(f32, @floatFromInt(used_font.baseSize)); var i: usize = 0; while (i < text.len) { var next: usize = 0; const letter = GetCodePointNext(text[i..], &next); const index = GetGlyphIndex(font, letter); i += next; if (letter == '\n') { text_offset_x = 0; text_offset_y += (font_size + textLineSpacing); } else { if (letter != ' ' and letter != '\t') { rl.drawTextCodepoint(font, letter, .{ .x = position.x + text_offset_x, .y = position.y + text_offset_y, }, font_size, tint); } if (font.glyphs[index].advanceX == 0) { text_offset_x += font.recs[index].width*scale_factor + spacing; } else { text_offset_x += @as(f32, @floatFromInt(font.glyphs[index].advanceX))*scale_factor + spacing; } } } } // Reimplementation of `rl.measureTextEx`, so a null terminated would not be required pub fn measureTextEx(font: rl.Font, text: []const u8, fontSize: f32, spacing: f32) rl.Vector2 { var textSize = rl.Vector2.init(0, 0); if (font.texture.id == 0) return textSize; // Security check var tempByteCounter: i32 = 0; // Used to count longer text line num chars var byteCounter: i32 = 0; var textWidth: f32 = 0; var tempTextWidth: f32 = 0; // Used to count longer text line width var textHeight: f32 = fontSize; const scaleFactor: f32 = fontSize/@as(f32, @floatFromInt(font.baseSize)); var i: usize = 0; while (i < text.len) { byteCounter += 1; var next: usize = 0; const letter = GetCodePointNext(text[i..], &next); const index = GetGlyphIndex(font, letter); i += next; if (letter != '\n') { if (font.glyphs[index].advanceX != 0) { textWidth += @floatFromInt(font.glyphs[index].advanceX); } else { textWidth += font.recs[index].width; textWidth += @floatFromInt(font.glyphs[index].offsetX); } } else { if (tempTextWidth < textWidth) tempTextWidth = textWidth; byteCounter = 0; textWidth = 0; textHeight += (fontSize + textLineSpacing); } if (tempByteCounter < byteCounter) tempByteCounter = byteCounter; } if (tempTextWidth < textWidth) tempTextWidth = textWidth; textSize.x = tempTextWidth*scaleFactor + @as(f32, @floatFromInt(tempByteCounter - 1)) * spacing; textSize.y = textHeight; return textSize; } pub fn drawTextCentered(font: rl.Font, text: []const u8, position: rl.Vector2, font_size: f32, spacing: f32, color: rl.Color) void { const text_size = measureTextEx(font, text, font_size, spacing); const adjusted_position = rl.Vector2{ .x = position.x - text_size.x/2, .y = position.y - text_size.y/2, }; drawTextEx(font, text, adjusted_position, font_size, spacing, color); }