fix: UTF-8 support in TTF drawText
- drawText now decodes UTF-8 codepoints instead of iterating bytes - Fixes rendering of accented characters (á, é, í, ó, ú, ñ, etc.) - Uses std.unicode.utf8ByteSequenceLength + utf8Decode - Invalid UTF-8 sequences are gracefully skipped 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
222c4e1542
commit
105e3c63d1
1 changed files with 26 additions and 10 deletions
|
|
@ -861,25 +861,41 @@ pub const TtfFont = struct {
|
||||||
var cx = x;
|
var cx = x;
|
||||||
const baseline_y = y + self.ascent();
|
const baseline_y = y + self.ascent();
|
||||||
|
|
||||||
for (text) |c| {
|
// UTF-8 iteration: decode codepoints instead of iterating bytes
|
||||||
if (c == '\n') continue;
|
var i: usize = 0;
|
||||||
if (c == ' ') {
|
while (i < text.len) {
|
||||||
const metrics = self.getGlyphMetrics(c);
|
// Get UTF-8 sequence length
|
||||||
|
const cp_len = std.unicode.utf8ByteSequenceLength(text[i]) catch {
|
||||||
|
i += 1; // Skip invalid byte
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (i + cp_len > text.len) break; // Not enough bytes
|
||||||
|
|
||||||
|
// Decode codepoint
|
||||||
|
const cp: u32 = std.unicode.utf8Decode(text[i..][0..cp_len]) catch {
|
||||||
|
i += cp_len;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
i += cp_len;
|
||||||
|
|
||||||
|
if (cp == '\n') continue;
|
||||||
|
if (cp == ' ') {
|
||||||
|
const metrics = self.getGlyphMetrics(cp);
|
||||||
cx += @intCast(metrics.advance);
|
cx += @intCast(metrics.advance);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get cached glyph or rasterize
|
// Try to get cached glyph or rasterize
|
||||||
const cache_key = makeCacheKey(c, self.render_size);
|
const cache_key = makeCacheKey(cp, self.render_size);
|
||||||
|
|
||||||
if (self.glyph_cache.get(cache_key)) |cached| {
|
if (self.glyph_cache.get(cache_key)) |cached| {
|
||||||
// Draw cached glyph
|
// Draw cached glyph
|
||||||
self.drawGlyphBitmap(fb, cx, baseline_y, cached, color, clip);
|
self.drawGlyphBitmap(fb, cx, baseline_y, cached, color, clip);
|
||||||
const metrics = self.getGlyphMetrics(c);
|
const metrics = self.getGlyphMetrics(cp);
|
||||||
cx += @intCast(metrics.advance);
|
cx += @intCast(metrics.advance);
|
||||||
} else {
|
} else {
|
||||||
// Rasterize and cache
|
// Rasterize and cache
|
||||||
const glyph_index = self.getGlyphIndex(c);
|
const glyph_index = self.getGlyphIndex(cp);
|
||||||
if (self.getGlyphOutline(glyph_index)) |outline| {
|
if (self.getGlyphOutline(glyph_index)) |outline| {
|
||||||
defer {
|
defer {
|
||||||
var outline_copy = outline;
|
var outline_copy = outline;
|
||||||
|
|
@ -895,9 +911,9 @@ pub const TtfFont = struct {
|
||||||
.height = @intCast(bitmap.height),
|
.height = @intCast(bitmap.height),
|
||||||
.bearing_x = @intCast(bitmap.bearing_x),
|
.bearing_x = @intCast(bitmap.bearing_x),
|
||||||
.bearing_y = @intCast(bitmap.bearing_y),
|
.bearing_y = @intCast(bitmap.bearing_y),
|
||||||
.advance = self.getGlyphMetrics(c).advance,
|
.advance = self.getGlyphMetrics(cp).advance,
|
||||||
},
|
},
|
||||||
.codepoint = c,
|
.codepoint = cp,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.glyph_cache.put(cache_key, cached_glyph) catch {};
|
self.glyph_cache.put(cache_key, cached_glyph) catch {};
|
||||||
|
|
@ -905,7 +921,7 @@ pub const TtfFont = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metrics = self.getGlyphMetrics(c);
|
const metrics = self.getGlyphMetrics(cp);
|
||||||
cx += @intCast(metrics.advance);
|
cx += @intCast(metrics.advance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue