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:
parent
c0107de978
commit
222c4e1542
5 changed files with 43 additions and 14 deletions
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
BIN
src/render/fonts/DroidSans.ttf
Normal file
BIN
src/render/fonts/DroidSans.ttf
Normal file
Binary file not shown.
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue