String Drawing System
How guideXOS renders text on screen
On This Page
Overview
guideXOS uses a custom bitmap font rendering system that draws text directly to the framebuffer. Unlike hardware-accelerated text rendering in modern operating systems, guideXOS implements pure software rendering with pixel-by-pixel drawing operations.
- Variable-width bitmap fonts
- Alpha transparency support
- Automatic line wrapping
- Newline character support
- Text measurement before rendering
Font System Architecture
IFont Class
The IFont
class in Kernel\Misc\IFont.cs handles all text rendering:
internal class IFont {
private readonly Image image; // PNG font texture
private readonly string charset; // Character mapping
public int FontSize; // Size of each character (e.g., 16px)
public IFont(Image _img, string _charset, int size) {
image = _img;
charset = _charset;
FontSize = size;
}
}
Font Structure
| Component | Description |
|---|---|
| Font Image | PNG file containing all characters in a grid layout |
| Charset | String mapping characters to positions (e.g., "ABC...xyz") |
| Font Size | Width/height of each character cell in pixels |
| NumRow | Number of characters per row in the font image |
Font Loading Example
// Load a 128px font from PNG
IFont font = new IFont(
new PNG(File.ReadAllBytes("Images/Yahei128.png")),
@"!""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
128 // Font size
);
// Used globally via WindowManager
WindowManager.font = font;
The Drawing Process
1. DrawString() Method
The main entry point for rendering text:
public void DrawString(int X, int Y, string Str,
int LineLimit = -1, int HeightLimit = -1) {
int w = 0, h = 0; // Current cursor position
for (int i = 0; i < Str.Length; i++) {
// Skip leading spaces on new lines
if (h != 0 && w == 0 && Str[i] == ' ')
continue;
// Draw character and advance cursor
w += DrawChar(Framebuffer.Graphics, X + w, Y + h, Str[i]);
// Handle line wrapping
if (w + FontSize > LineLimit && LineLimit != -1 || Str[i] == '\n') {
w = 0; // Reset to start of line
h += FontSize; // Move down one line
if (HeightLimit != -1 && h >= HeightLimit)
return; // Stop if height exceeded
}
}
}
2. DrawChar() - Character Rendering
Renders a single character pixel-by-pixel:
public int DrawChar(Graphics g, int X, int Y, char Chr) {
// Step 1: Find character in charset
int index = charset.IndexOf(Chr);
if (index == -1) {
if (Chr == ' ') return FontSize / 2; // Space is half width
return 0; // Unknown char = skip
}
// Step 2: Calculate position in font texture
int baseX = 0, baseY = 0;
for (int i = 0; i <= index; i++) {
if ((i % NumRow) == 0 && i != 0) {
baseX = 0;
baseY += FontSize;
}
if (i != index)
baseX += FontSize;
}
// Step 3: Draw character pixel-by-pixel
for (int w = 0; w < FontSize; w++) {
int counter = 0;
for (int h = 0; h < FontSize; h++) {
uint color = image.GetPixel(baseX + w, baseY + h);
if (X != -1 && Y != -1)
g.DrawPoint(X + w, Y + h, color, true); // Alpha blend
if ((color & 0xFF000000) == 0) counter++;
}
// Step 4: Variable-width - stop at empty column
if (w > (FontSize / 3) && counter == FontSize)
return w; // Return actual width used
}
return FontSize; // Full width if no empty column
}
Rendering Pipeline
DrawString("Hello")
?
For each character 'H', 'e', 'l', 'l', 'o':
?
1. Find character index in charset
2. Calculate texture coordinates (baseX, baseY)
3. Extract character bitmap from font image
4. Draw pixel-by-pixel to framebuffer
5. Apply alpha transparency
6. Detect actual character width
7. Advance cursor position
?
Return total width used
Usage Examples
Simple Text
// Draw text at position (100, 50)
WindowManager.font.DrawString(100, 50, "Hello World");
Text with Line Wrapping
// Wrap text at 300px width, limit to 2 lines
WindowManager.font.DrawString(
100, // X position
50, // Y position
"This is a long text that will wrap",
300, // Line width limit
WindowManager.font.FontSize * 2 // Height limit (2 lines)
);
Centered Text
// Measure text width first
string text = "Centered Text";
int textWidth = WindowManager.font.MeasureString(text);
// Calculate center position
int x = (screenWidth / 2) - (textWidth / 2);
int y = (screenHeight / 2) - (WindowManager.font.FontSize / 2);
// Draw centered
WindowManager.font.DrawString(x, y, text);
Measuring Text (No Drawing)
// Calculate width without rendering
int width = WindowManager.font.MeasureString("Sample Text");
// Useful for:
// - Layout calculations
// - Button sizing
// - Text centering
// - Clipping detection
Multi-line Text with Newlines
// Newlines are automatically handled
WindowManager.font.DrawString(100, 50,
"Line 1\nLine 2\nLine 3");
Performance Characteristics
Rendering Speed
| Operation | Complexity | Notes |
|---|---|---|
| DrawChar() | O(FontSize²) | Pixel-by-pixel loop (e.g., 16×16 = 256 pixels) |
| DrawString() | O(n × FontSize²) | n = string length |
| MeasureString() | O(n × FontSize²) | Same as drawing (no optimization) |
| Character lookup | O(charset length) | Linear search in charset string |
Optimization Strategies
- Variable-width fonts - Characters only use pixels they need
- Early termination - Stop drawing when empty column detected
- No memory allocation - All drawing uses stack variables
- Direct framebuffer access - Bypasses abstraction layers
Text rendering is CPU-intensive. A 16px font character requires ~256 pixel operations. Drawing a full screen of text (1920×1080) can take several milliseconds. Use StringPool to avoid repeated string allocations in render loops.
StringPool Integration
Why StringPool Matters
Since text rendering happens every frame in the GUI, creating new strings causes severe memory leaks. The StringPool system caches commonly used strings to eliminate allocations.
Problem Without StringPool
// ? BAD: Allocates new string every frame
void OnDraw() {
int cpuUsage = 42;
string text = cpuUsage.ToString() + "%"; // New allocation
WindowManager.font.DrawString(100, 50, text);
// Memory leak: 'text' is never freed
}
// Result: ~8 MB/minute leaked in Task Manager alone!
Solution With StringPool
// ? GOOD: Reuses cached string
void OnDraw() {
int cpuUsage = 42;
string text = StringPool.GetPercentage(cpuUsage); // Cached "42%"
WindowManager.font.DrawString(100, 50, text);
// No memory leak: string is reused, never disposed
}
// Result: Zero allocations after warm-up
StringPool API
| Method | Usage | Cache Size |
|---|---|---|
GetPercentage(int) |
Returns "0%" to "100%" | 101 entries |
GetNumber(int) |
Returns "0" to "10,000" | 10,001 entries |
GetMemorySize(ulong) |
Returns "42 KB", "128 MB", "2 GB", etc. | 2,000 entries (LRU) |
GetTransferRate(int) |
Returns "42 KB/s", "5 MB/s", etc. | Uses GetNumber internally |
FormatUptime(ulong) |
Returns "12:34:56" format | Composed from GetNumber |
Best Practices
- ? Use StringPool for any strings in render loops
- ? Cache strings that update infrequently (update every second, not every frame)
- ? Reuse pooled strings - never call Dispose() on them
- ? Avoid ToString() in OnDraw() methods
- ? Avoid string concatenation in high-frequency code
- ? Avoid string.Format() - use StringPool methods instead
Using StringPool in TaskManager, Monitor, and PerformanceWidget reduced memory leak from 8.3 MB/minute to near zero. Free/Alloc ratio improved from 72% to 95%+.
Technical Details
Font File Format
guideXOS fonts are stored as PNG images with characters laid out in a grid:
- Each character occupies a
FontSize × FontSizecell - Characters are arranged left-to-right, top-to-bottom
- Order matches the
charsetstring parameter - Transparent pixels (alpha = 0) are not drawn
- Colored pixels are alpha-blended with background
Available Fonts
| Font File | Size | Usage |
|---|---|---|
| Yahei128.png | 128px | Lock screen, large text |
| Default system font | 16px | WindowManager.font (all GUI) |
Limitations
- ? No font scaling (must load multiple sizes)
- ? No anti-aliasing (except alpha from PNG)
- ? No kerning adjustments
- ? No Unicode support (ASCII/extended ASCII only)
- ? No right-to-left or vertical text
- ? No hardware acceleration