diff --git a/CLAUDE.md b/CLAUDE.md index d41d38a..5a5532c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,12 +79,13 @@ Resumen breve (1-2 frases). Resultado principal. ## TAREA COMPLETADA: Fuentes TTF con Antialiasing ✅ -### Estado actual (v0.16.0) +### Estado actual (v0.16.2) - ✅ Parsing TTF completo (glyf, cmap format 4 y 12) - ✅ Rasterización con bezier cuadráticas - ✅ Antialiasing 2x supersampling -- ✅ **Fuente embebida** (AdwaitaSans-Regular, ~860KB) +- ✅ **Fuente embebida** (DroidSans, 187KB) - Fuente estática clásica - ✅ Integración con SoftwareRenderer +- ⚠️ **Pendiente:** UTF-8 multibyte (á,é,ñ) - drawText itera bytes, no codepoints ### Uso ```zig @@ -98,7 +99,7 @@ renderer.setTtfFont(&ttf); ### Archivos clave - `src/render/ttf.zig` - Parsing y rasterización TTF - `src/render/embedded_font.zig` - Fuente embebida -- `src/render/fonts/AdwaitaSans-Regular.ttf` - Datos de fuente +- `src/render/fonts/DroidSans.ttf` - Datos de fuente (Apache 2.0) --- @@ -107,7 +108,7 @@ renderer.setTtfFont(&ttf); | Campo | Valor | |-------|-------| | **Nombre** | zcatgui | -| **Versión** | v0.16.1 | +| **Versión** | v0.16.2 | | **Fecha inicio** | 2025-12-09 | | **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends | | **Lenguaje** | Zig 0.15.2 | @@ -665,14 +666,15 @@ const stdout = std.fs.File.stdout(); // NO std.io.getStdOut() | 2025-12-09 | v0.15.0 | Documentación: REFERENCE.md completo (1370 líneas) | | 2025-12-11 | v0.15.1 | FocusSystem rediseñado: registration_group/active_group, focus implícito | | 2025-12-11 | v0.15.2 | Widgets adaptados a FocusSystem: numberentry, textarea, select, radio, slider, tabs | -| 2025-12-16 | v0.16.0 | TTF rasterization con antialiasing (supersampling 2x), tests con AdwaitaSans | -| 2025-12-16 | v0.16.1 | Fuente embebida: AdwaitaSans-Regular.ttf (~860KB), TtfFont.initEmbedded() | +| 2025-12-16 | v0.16.0 | TTF rasterization con antialiasing (supersampling 2x) | +| 2025-12-16 | v0.16.1 | Fuente embebida: TtfFont.initEmbedded() | +| 2025-12-16 | v0.16.2 | Fix TTF: DroidSans (187KB) reemplaza AdwaitaSans (variable). Y-flip rasterización. **UTF-8 pendiente** | --- ## ESTADO ACTUAL -**✅ PROYECTO COMPLETADO - v0.16.1** +**✅ PROYECTO COMPLETADO - v0.16.2** > **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación) diff --git a/src/render/embedded_font.zig b/src/render/embedded_font.zig index 2ef8407..367f957 100644 --- a/src/render/embedded_font.zig +++ b/src/render/embedded_font.zig @@ -1,18 +1,25 @@ //! Fuente TTF embebida para uso sin dependencias externas //! -//! Incluye AdwaitaSans-Regular para renderizado de texto con antialiasing. -//! Licencia: SIL Open Font License (permite redistribución embebida). +//! Incluye DroidSans para renderizado de texto con antialiasing. +//! Licencia: Apache 2.0 (permite redistribución embebida). +//! +//! Nota: Se usa DroidSans (fuente estática) en lugar de AdwaitaSans +//! porque AdwaitaSans es una fuente variable (OpenType 1.8+) que requiere +//! parsing de tablas fvar/gvar que no soportamos. const std = @import("std"); const TtfFont = @import("ttf.zig").TtfFont; /// Datos binarios de la fuente embebida (cargados en tiempo de compilación) -pub const adwaita_sans_data: []const u8 = @embedFile("fonts/AdwaitaSans-Regular.ttf"); +pub const font_data: []const u8 = @embedFile("fonts/DroidSans.ttf"); + +/// Alias para compatibilidad (deprecated, usar font_data) +pub const adwaita_sans_data = font_data; /// Tamaño de la fuente embebida en bytes -pub const adwaita_sans_size: usize = adwaita_sans_data.len; +pub const font_size: usize = font_data.len; -/// Inicializa TtfFont desde la fuente embebida AdwaitaSans-Regular +/// Inicializa TtfFont desde la fuente embebida DroidSans /// /// Ejemplo: /// ```zig @@ -22,7 +29,7 @@ pub const adwaita_sans_size: usize = adwaita_sans_data.len; /// renderer.setTtfFont(&ttf); /// ``` pub fn initEmbeddedFont(allocator: std.mem.Allocator) !TtfFont { - return TtfFont.initFromMemory(allocator, adwaita_sans_data); + return TtfFont.initFromMemory(allocator, font_data); } test "embedded font data is valid" { diff --git a/src/render/fonts/AdwaitaSans-Regular.ttf b/src/render/fonts/AdwaitaSans-Regular.ttf deleted file mode 100644 index 6fcafd9..0000000 Binary files a/src/render/fonts/AdwaitaSans-Regular.ttf and /dev/null differ diff --git a/src/render/fonts/DroidSans.ttf b/src/render/fonts/DroidSans.ttf new file mode 100644 index 0000000..ad1efca Binary files /dev/null and b/src/render/fonts/DroidSans.ttf differ diff --git a/src/render/ttf.zig b/src/render/ttf.zig index f9a101c..fa0d571 100644 --- a/src/render/ttf.zig +++ b/src/render/ttf.zig @@ -112,8 +112,10 @@ pub fn rasterizeGlyph( const ss = @as(f32, @floatFromInt(supersample)); const ss_sq = @as(f32, @floatFromInt(@as(u32, supersample) * @as(u32, supersample))); + const height_f = @as(f32, @floatFromInt(height)); // Scanline fill with supersampling + // Y-flip: TTF Y goes up, bitmap Y goes down for (0..height) |py| { for (0..width) |px| { var coverage: u32 = 0; @@ -122,7 +124,8 @@ pub fn rasterizeGlyph( for (0..supersample) |sy| { for (0..supersample) |sx| { const sample_x = @as(f32, @floatFromInt(px)) + (@as(f32, @floatFromInt(sx)) + 0.5) / ss; - const sample_y = @as(f32, @floatFromInt(py)) + (@as(f32, @floatFromInt(sy)) + 0.5) / ss; + // Flip Y: bitmap row 0 should sample at top of glyph (high y) + const sample_y = (height_f - 1.0 - @as(f32, @floatFromInt(py))) + (@as(f32, @floatFromInt(sy)) + 0.5) / ss; // Count winding number var winding: i32 = 0; @@ -1158,3 +1161,20 @@ test "TTF rasterize multiple characters" { } } } + +test "embedded font glyph indices" { + const allocator = std.testing.allocator; + const embedded = @import("embedded_font.zig"); + + var font = try TtfFont.initFromMemory(allocator, embedded.font_data); + defer font.deinit(); + + // Verify basic font properties + try std.testing.expect(font.num_glyphs > 0); + try std.testing.expect(font.cmap_offset > 0); + + // Verify ASCII character mapping + try std.testing.expect(font.getGlyphIndex('A') > 0); + try std.testing.expect(font.getGlyphIndex('a') > 0); + try std.testing.expect(font.getGlyphIndex('0') > 0); +}