fix: DroidSans reemplaza AdwaitaSans (variable) + Y-flip TTF

- AdwaitaSans era fuente variable (fvar/gvar) incompatible con parser
- DroidSans.ttf: fuente estática 187KB (Apache 2.0)
- Y-flip en rasterización: TTF Y crece arriba, bitmap Y crece abajo
- Pendiente: UTF-8 multibyte en drawText

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
reugenio 2025-12-16 13:19:40 +01:00
parent c0107de978
commit 222c4e1542
5 changed files with 43 additions and 14 deletions

View file

@ -79,12 +79,13 @@ Resumen breve (1-2 frases). Resultado principal.
## TAREA COMPLETADA: Fuentes TTF con Antialiasing ✅ ## 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) - ✅ Parsing TTF completo (glyf, cmap format 4 y 12)
- ✅ Rasterización con bezier cuadráticas - ✅ Rasterización con bezier cuadráticas
- ✅ Antialiasing 2x supersampling - ✅ Antialiasing 2x supersampling
- ✅ **Fuente embebida** (AdwaitaSans-Regular, ~860KB) - ✅ **Fuente embebida** (DroidSans, 187KB) - Fuente estática clásica
- ✅ Integración con SoftwareRenderer - ✅ Integración con SoftwareRenderer
- ⚠️ **Pendiente:** UTF-8 multibyte (á,é,ñ) - drawText itera bytes, no codepoints
### Uso ### Uso
```zig ```zig
@ -98,7 +99,7 @@ renderer.setTtfFont(&ttf);
### Archivos clave ### Archivos clave
- `src/render/ttf.zig` - Parsing y rasterización TTF - `src/render/ttf.zig` - Parsing y rasterización TTF
- `src/render/embedded_font.zig` - Fuente embebida - `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 | | Campo | Valor |
|-------|-------| |-------|-------|
| **Nombre** | zcatgui | | **Nombre** | zcatgui |
| **Versión** | v0.16.1 | | **Versión** | v0.16.2 |
| **Fecha inicio** | 2025-12-09 | | **Fecha inicio** | 2025-12-09 |
| **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends | | **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends |
| **Lenguaje** | Zig 0.15.2 | | **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-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.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-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.0 | TTF rasterization con antialiasing (supersampling 2x) |
| 2025-12-16 | v0.16.1 | Fuente embebida: AdwaitaSans-Regular.ttf (~860KB), TtfFont.initEmbedded() | | 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 ## 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) > **Para detalles técnicos completos, ver `REFERENCE.md`** (1370 líneas de documentación)

View file

@ -1,18 +1,25 @@
//! Fuente TTF embebida para uso sin dependencias externas //! Fuente TTF embebida para uso sin dependencias externas
//! //!
//! Incluye AdwaitaSans-Regular para renderizado de texto con antialiasing. //! Incluye DroidSans para renderizado de texto con antialiasing.
//! Licencia: SIL Open Font License (permite redistribución embebida). //! 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 std = @import("std");
const TtfFont = @import("ttf.zig").TtfFont; const TtfFont = @import("ttf.zig").TtfFont;
/// Datos binarios de la fuente embebida (cargados en tiempo de compilación) /// 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 /// 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: /// Ejemplo:
/// ```zig /// ```zig
@ -22,7 +29,7 @@ pub const adwaita_sans_size: usize = adwaita_sans_data.len;
/// renderer.setTtfFont(&ttf); /// renderer.setTtfFont(&ttf);
/// ``` /// ```
pub fn initEmbeddedFont(allocator: std.mem.Allocator) !TtfFont { 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" { test "embedded font data is valid" {

Binary file not shown.

View file

@ -112,8 +112,10 @@ pub fn rasterizeGlyph(
const ss = @as(f32, @floatFromInt(supersample)); const ss = @as(f32, @floatFromInt(supersample));
const ss_sq = @as(f32, @floatFromInt(@as(u32, supersample) * @as(u32, supersample))); const ss_sq = @as(f32, @floatFromInt(@as(u32, supersample) * @as(u32, supersample)));
const height_f = @as(f32, @floatFromInt(height));
// Scanline fill with supersampling // Scanline fill with supersampling
// Y-flip: TTF Y goes up, bitmap Y goes down
for (0..height) |py| { for (0..height) |py| {
for (0..width) |px| { for (0..width) |px| {
var coverage: u32 = 0; var coverage: u32 = 0;
@ -122,7 +124,8 @@ pub fn rasterizeGlyph(
for (0..supersample) |sy| { for (0..supersample) |sy| {
for (0..supersample) |sx| { for (0..supersample) |sx| {
const sample_x = @as(f32, @floatFromInt(px)) + (@as(f32, @floatFromInt(sx)) + 0.5) / ss; 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 // Count winding number
var winding: i32 = 0; 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);
}