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 |
|-------|-------|
| **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)

View file

@ -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

View file

@ -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;

View file

@ -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);
}