diff --git a/CLAUDE.md b/CLAUDE.md index 8c34139..ca3b61e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,7 +104,7 @@ zsimifactu se ve "años 90" con fuentes bitmap 8x16. Necesitamos TTF con antiali | Campo | Valor | |-------|-------| | **Nombre** | zcatgui | -| **Versión** | v0.15.0 | +| **Versión** | v0.16.0 | | **Fecha inicio** | 2025-12-09 | | **Estado** | ✅ COMPLETO - 37 widgets, ~35K LOC, 4 backends | | **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-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 | --- ## 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) diff --git a/REFERENCE.md b/REFERENCE.md index 09c7854..99a2e53 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,6 +1,6 @@ # zcatgui Reference Manual -**Version**: 0.15.0 +**Version**: 0.16.0 **Language**: Zig 0.15.2 **Paradigm**: Immediate Mode GUI **Lines of Code**: ~35,000 across 81 source files diff --git a/src/render/software.zig b/src/render/software.zig index 4c8a8a6..2c999c6 100644 --- a/src/render/software.zig +++ b/src/render/software.zig @@ -29,6 +29,7 @@ const Style = @import("../core/style.zig"); const Layout = @import("../core/layout.zig"); const Framebuffer = @import("framebuffer.zig").Framebuffer; const Font = @import("font.zig").Font; +const TtfFont = @import("ttf.zig").TtfFont; const Color = Style.Color; const Rect = Layout.Rect; @@ -38,6 +39,8 @@ const DrawCommand = Command.DrawCommand; pub const SoftwareRenderer = struct { framebuffer: *Framebuffer, default_font: ?*Font, + /// TTF font (takes priority over bitmap if set) + ttf_font: ?*TtfFont = null, /// Clipping stack clip_stack: [16]Rect, @@ -50,16 +53,27 @@ pub const SoftwareRenderer = struct { return .{ .framebuffer = framebuffer, .default_font = null, + .ttf_font = null, .clip_stack = undefined, .clip_depth = 0, }; } - /// Set the default font + /// Set the default bitmap font pub fn setDefaultFont(self: *Self, font: *Font) void { 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 pub fn getClip(self: Self) Rect { if (self.clip_depth == 0) { @@ -152,13 +166,20 @@ pub const SoftwareRenderer = struct { } 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| @as(*Font, @ptrCast(@alignCast(f))) else self.default_font orelse return; - const clip = self.getClip(); - // UTF-8 text rendering - decode codepoints properly var x = t.x; var i: usize = 0; diff --git a/src/render/ttf.zig b/src/render/ttf.zig index 58b9163..40ffc91 100644 --- a/src/render/ttf.zig +++ b/src/render/ttf.zig @@ -103,11 +103,11 @@ pub fn rasterizeGlyph( @memset(data, 0); // Collect all edges from contours - var edges_list = std.ArrayList(Edge).init(allocator); - defer edges_list.deinit(); + var edges_list: std.ArrayListUnmanaged(Edge) = .{}; + defer edges_list.deinit(allocator); 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)); @@ -154,7 +154,8 @@ pub fn rasterizeGlyph( /// Collect edges from a contour, handling bezier curves fn collectEdgesFromContour( - edges: *std.ArrayList(Edge), + allocator: Allocator, + edges: *std.ArrayListUnmanaged(Edge), points: []const GlyphPoint, scale: f32, x_off: f32, @@ -177,7 +178,7 @@ fn collectEdgesFromContour( if (p0.on_curve and p1.on_curve) { // Straight line - try addEdge(edges, x0, y0, x1, y1); + try addEdge(allocator, edges, x0, y0, x1, y1); i += 1; } else if (p0.on_curve and !p1.on_curve) { // Bezier curve: p0 is on, p1 is control @@ -197,7 +198,7 @@ fn collectEdgesFromContour( } // 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 { // Off-curve start: should have been handled, skip i += 1; @@ -207,7 +208,8 @@ fn collectEdgesFromContour( /// Subdivide quadratic bezier curve into line segments fn subdivideBezier( - edges: *std.ArrayList(Edge), + allocator: Allocator, + edges: *std.ArrayListUnmanaged(Edge), x0: f32, y0: 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_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_y = curr_y; @@ -236,7 +238,7 @@ fn subdivideBezier( } /// 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 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 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 { - 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) { fb.setPixel(@intCast(screen_x), @intCast(screen_y), color); } 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); fb.setPixel(@intCast(screen_x), @intCast(screen_y), blended); }