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:
parent
0625e18e77
commit
a11e1ea842
4 changed files with 49 additions and 18 deletions
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue