feat: zcatgui v0.16.0 - TTF rasterization con antialiasing

Cambios:
- TTF rasterization completo con bezier curves
- Antialiasing via supersampling 2x
- Integración TTF en SoftwareRenderer (setTtfFont)
- Fix ArrayListUnmanaged para Zig 0.15.2
- Documentación REFERENCE.md actualizada

CLAUDE.md actualizado con v0.16.0 y historial.

🤖 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 01:05:48 +01:00
parent 0625e18e77
commit a11e1ea842
4 changed files with 49 additions and 18 deletions

View file

@ -104,7 +104,7 @@ zsimifactu se ve "años 90" con fuentes bitmap 8x16. Necesitamos TTF con antiali
| Campo | Valor | | Campo | Valor |
|-------|-------| |-------|-------|
| **Nombre** | zcatgui | | **Nombre** | zcatgui |
| **Versión** | v0.15.0 | | **Versión** | v0.16.0 |
| **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 |
@ -662,12 +662,13 @@ 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 |
--- ---
## ESTADO ACTUAL ## ESTADO ACTUAL
**✅ PROYECTO COMPLETADO - v0.15.0** **✅ PROYECTO COMPLETADO - v0.16.0**
> **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,6 +1,6 @@
# zcatgui Reference Manual # zcatgui Reference Manual
**Version**: 0.15.0 **Version**: 0.16.0
**Language**: Zig 0.15.2 **Language**: Zig 0.15.2
**Paradigm**: Immediate Mode GUI **Paradigm**: Immediate Mode GUI
**Lines of Code**: ~35,000 across 81 source files **Lines of Code**: ~35,000 across 81 source files

View file

@ -29,6 +29,7 @@ const Style = @import("../core/style.zig");
const Layout = @import("../core/layout.zig"); const Layout = @import("../core/layout.zig");
const Framebuffer = @import("framebuffer.zig").Framebuffer; const Framebuffer = @import("framebuffer.zig").Framebuffer;
const Font = @import("font.zig").Font; const Font = @import("font.zig").Font;
const TtfFont = @import("ttf.zig").TtfFont;
const Color = Style.Color; const Color = Style.Color;
const Rect = Layout.Rect; const Rect = Layout.Rect;
@ -38,6 +39,8 @@ const DrawCommand = Command.DrawCommand;
pub const SoftwareRenderer = struct { pub const SoftwareRenderer = struct {
framebuffer: *Framebuffer, framebuffer: *Framebuffer,
default_font: ?*Font, default_font: ?*Font,
/// TTF font (takes priority over bitmap if set)
ttf_font: ?*TtfFont = null,
/// Clipping stack /// Clipping stack
clip_stack: [16]Rect, clip_stack: [16]Rect,
@ -50,16 +53,27 @@ pub const SoftwareRenderer = struct {
return .{ return .{
.framebuffer = framebuffer, .framebuffer = framebuffer,
.default_font = null, .default_font = null,
.ttf_font = null,
.clip_stack = undefined, .clip_stack = undefined,
.clip_depth = 0, .clip_depth = 0,
}; };
} }
/// Set the default font /// Set the default bitmap font
pub fn setDefaultFont(self: *Self, font: *Font) void { pub fn setDefaultFont(self: *Self, font: *Font) void {
self.default_font = font; self.default_font = font;
} }
/// Set the TTF font (takes priority over bitmap font)
pub fn setTtfFont(self: *Self, font: *TtfFont) void {
self.ttf_font = font;
}
/// Clear the TTF font (revert to bitmap)
pub fn clearTtfFont(self: *Self) void {
self.ttf_font = null;
}
/// Get the current clip rectangle /// Get the current clip rectangle
pub fn getClip(self: Self) Rect { pub fn getClip(self: Self) Rect {
if (self.clip_depth == 0) { if (self.clip_depth == 0) {
@ -152,13 +166,20 @@ pub const SoftwareRenderer = struct {
} }
fn drawText(self: *Self, t: Command.TextCommand) void { fn drawText(self: *Self, t: Command.TextCommand) void {
const clip = self.getClip();
// Use TTF font if available (takes priority)
if (self.ttf_font) |ttf| {
ttf.drawText(self.framebuffer, t.x, t.y, t.text, t.color, clip);
return;
}
// Fall back to bitmap font
const font = if (t.font) |f| const font = if (t.font) |f|
@as(*Font, @ptrCast(@alignCast(f))) @as(*Font, @ptrCast(@alignCast(f)))
else else
self.default_font orelse return; self.default_font orelse return;
const clip = self.getClip();
// UTF-8 text rendering - decode codepoints properly // UTF-8 text rendering - decode codepoints properly
var x = t.x; var x = t.x;
var i: usize = 0; var i: usize = 0;

View file

@ -103,11 +103,11 @@ pub fn rasterizeGlyph(
@memset(data, 0); @memset(data, 0);
// Collect all edges from contours // Collect all edges from contours
var edges_list = std.ArrayList(Edge).init(allocator); var edges_list: std.ArrayListUnmanaged(Edge) = .{};
defer edges_list.deinit(); defer edges_list.deinit(allocator);
for (outline.contours) |contour| { for (outline.contours) |contour| {
collectEdgesFromContour(&edges_list, contour.points, scale, x_min_f, y_min_f, supersample) catch return null; collectEdgesFromContour(allocator, &edges_list, contour.points, scale, x_min_f, y_min_f, supersample) catch return null;
} }
const ss = @as(f32, @floatFromInt(supersample)); const ss = @as(f32, @floatFromInt(supersample));
@ -154,7 +154,8 @@ pub fn rasterizeGlyph(
/// Collect edges from a contour, handling bezier curves /// Collect edges from a contour, handling bezier curves
fn collectEdgesFromContour( fn collectEdgesFromContour(
edges: *std.ArrayList(Edge), allocator: Allocator,
edges: *std.ArrayListUnmanaged(Edge),
points: []const GlyphPoint, points: []const GlyphPoint,
scale: f32, scale: f32,
x_off: f32, x_off: f32,
@ -177,7 +178,7 @@ fn collectEdgesFromContour(
if (p0.on_curve and p1.on_curve) { if (p0.on_curve and p1.on_curve) {
// Straight line // Straight line
try addEdge(edges, x0, y0, x1, y1); try addEdge(allocator, edges, x0, y0, x1, y1);
i += 1; i += 1;
} else if (p0.on_curve and !p1.on_curve) { } else if (p0.on_curve and !p1.on_curve) {
// Bezier curve: p0 is on, p1 is control // Bezier curve: p0 is on, p1 is control
@ -197,7 +198,7 @@ fn collectEdgesFromContour(
} }
// Subdivide bezier curve // Subdivide bezier curve
try subdivideBezier(edges, x0, y0, x1, y1, x2, y2, subdivisions * 2); try subdivideBezier(allocator, edges, x0, y0, x1, y1, x2, y2, subdivisions * 2);
} else { } else {
// Off-curve start: should have been handled, skip // Off-curve start: should have been handled, skip
i += 1; i += 1;
@ -207,7 +208,8 @@ fn collectEdgesFromContour(
/// Subdivide quadratic bezier curve into line segments /// Subdivide quadratic bezier curve into line segments
fn subdivideBezier( fn subdivideBezier(
edges: *std.ArrayList(Edge), allocator: Allocator,
edges: *std.ArrayListUnmanaged(Edge),
x0: f32, x0: f32,
y0: f32, y0: f32,
cx: f32, cx: f32,
@ -228,7 +230,7 @@ fn subdivideBezier(
const curr_x = t1 * t1 * x0 + 2.0 * t1 * t * cx + t * t * x1; const curr_x = t1 * t1 * x0 + 2.0 * t1 * t * cx + t * t * x1;
const curr_y = t1 * t1 * y0 + 2.0 * t1 * t * cy + t * t * y1; const curr_y = t1 * t1 * y0 + 2.0 * t1 * t * cy + t * t * y1;
try addEdge(edges, prev_x, prev_y, curr_x, curr_y); try addEdge(allocator, edges, prev_x, prev_y, curr_x, curr_y);
prev_x = curr_x; prev_x = curr_x;
prev_y = curr_y; prev_y = curr_y;
@ -236,7 +238,7 @@ fn subdivideBezier(
} }
/// Add edge if it's not horizontal /// Add edge if it's not horizontal
fn addEdge(edges: *std.ArrayList(Edge), x0: f32, y0: f32, x1: f32, y1: f32) !void { fn addEdge(allocator: Allocator, edges: *std.ArrayListUnmanaged(Edge), x0: f32, y0: f32, x1: f32, y1: f32) !void {
// Skip horizontal edges // Skip horizontal edges
if (@abs(y1 - y0) < 0.001) return; if (@abs(y1 - y0) < 0.001) return;
@ -245,9 +247,9 @@ fn addEdge(edges: *std.ArrayList(Edge), x0: f32, y0: f32, x1: f32, y1: f32) !voi
// Always store with y0 < y1 // Always store with y0 < y1
if (y0 < y1) { if (y0 < y1) {
try edges.append(Edge{ .x0 = x0, .y0 = y0, .x1 = x1, .y1 = y1, .direction = direction }); try edges.append(allocator, Edge{ .x0 = x0, .y0 = y0, .x1 = x1, .y1 = y1, .direction = direction });
} else { } else {
try edges.append(Edge{ .x0 = x1, .y0 = y1, .x1 = x0, .y1 = y0, .direction = direction }); try edges.append(allocator, Edge{ .x0 = x1, .y0 = y1, .x1 = x0, .y1 = y0, .direction = direction });
} }
} }
@ -937,7 +939,14 @@ pub const TtfFont = struct {
if (alpha == 255) { if (alpha == 255) {
fb.setPixel(@intCast(screen_x), @intCast(screen_y), color); fb.setPixel(@intCast(screen_x), @intCast(screen_y), color);
} else { } else {
const bg = fb.getPixel(@intCast(screen_x), @intCast(screen_y)); // Get background pixel and convert u32 to Color
const bg_u32 = fb.getPixel(@intCast(screen_x), @intCast(screen_y)) orelse 0;
const bg = Color{
.r = @intCast((bg_u32 >> 24) & 0xFF),
.g = @intCast((bg_u32 >> 16) & 0xFF),
.b = @intCast((bg_u32 >> 8) & 0xFF),
.a = @intCast(bg_u32 & 0xFF),
};
const blended = blendColors(color, bg, alpha); const blended = blendColors(color, bg, alpha);
fb.setPixel(@intCast(screen_x), @intCast(screen_y), blended); fb.setPixel(@intCast(screen_x), @intCast(screen_y), blended);
} }