a73x

c6a25e27

Add Atlas.reset and Face.reinit for scale changes

a73x   2026-04-09 10:19

Commit message
Add Atlas.reset and Face.reinit for scale changes

Atlas.reset() zeroes pixel memory, restores the cursor-white pixel at
(0,0), rewinds packing cursors, clears the glyph cache, and marks the
atlas dirty. Face.reinit() replaces the underlying FT_Face at a new
px_size without re-creating the FT_Library.

These are the primitives Task 5/6 need to rebuild rasterization when
the wl_surface buffer scale changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

src/font.zig
Old New
@@ -85,6 +85,25 @@ pub const Face = struct {
85 _ = c.FT_Done_FreeType(self.library); 85 _ = c.FT_Done_FreeType(self.library);
86 } 86 }
87 87
88 pub fn reinit(
89 self: *Face,
90 path: [:0]const u8,
91 index: c_int,
92 px_size: u32,
93 ) !void {
94 _ = c.FT_Done_Face(self.face);
95 self.face = null;
96
97 var new_face: c.FT_Face = null;
98 if (c.FT_New_Face(self.library, path.ptr, index, &new_face) != 0) return error.FtNewFaceFailed;
99 errdefer _ = c.FT_Done_Face(new_face);
100
101 if (c.FT_Set_Pixel_Sizes(new_face, 0, px_size) != 0) return error.FtSetPixelSizesFailed;
102
103 self.face = new_face;
104 self.px_size = px_size;
105 }
106
88 pub fn rasterize(self: *Face, codepoint: u21) !Glyph { 107 pub fn rasterize(self: *Face, codepoint: u21) !Glyph {
89 const glyph_index = c.FT_Get_Char_Index(self.face, codepoint); 108 const glyph_index = c.FT_Get_Char_Index(self.face, codepoint);
90 if (c.FT_Load_Glyph(self.face, glyph_index, c.FT_LOAD_RENDER) != 0) { 109 if (c.FT_Load_Glyph(self.face, glyph_index, c.FT_LOAD_RENDER) != 0) {
@@ -191,6 +210,16 @@ pub const Atlas = struct {
191 self.cache.deinit(); 210 self.cache.deinit();
192 } 211 }
193 212
213 pub fn reset(self: *Atlas) void {
214 @memset(self.pixels, 0);
215 self.pixels[0] = 255;
216 self.cursor_x = 1;
217 self.cursor_y = 0;
218 self.row_height = 1;
219 self.cache.clearRetainingCapacity();
220 self.dirty = true;
221 }
222
194 pub fn cursorUV(self: *const Atlas) GlyphUV { 223 pub fn cursorUV(self: *const Atlas) GlyphUV {
195 return .{ 224 return .{
196 .u0 = 0, 225 .u0 = 0,
@@ -301,3 +330,39 @@ test "Atlas reserves a white pixel for cursor rendering" {
301 try std.testing.expectEqual(@as(f32, 0), uv.u0); 330 try std.testing.expectEqual(@as(f32, 0), uv.u0);
302 try std.testing.expectEqual(@as(f32, 0), uv.v0); 331 try std.testing.expectEqual(@as(f32, 0), uv.v0);
303 } 332 }
333
334 test "Atlas.reset clears cache and starts fresh" {
335 var lookup = try lookupConfiguredFont(std.testing.allocator);
336 defer lookup.deinit(std.testing.allocator);
337
338 var face = try Face.init(std.testing.allocator, lookup.path, lookup.index, 14);
339 defer face.deinit();
340
341 var atlas = try Atlas.init(std.testing.allocator, 256, 256);
342 defer atlas.deinit();
343
344 _ = try atlas.getOrInsert(&face, 'A');
345 try std.testing.expect(atlas.cache.count() > 0);
346
347 atlas.reset();
348 try std.testing.expectEqual(@as(u32, 0), atlas.cache.count());
349 try std.testing.expectEqual(@as(u8, 255), atlas.pixels[0]);
350 try std.testing.expect(atlas.dirty);
351
352 // Re-inserting the same glyph should succeed after reset.
353 _ = try atlas.getOrInsert(&face, 'A');
354 }
355
356 test "Face.reinit switches px_size and produces different cell metrics" {
357 var lookup = try lookupConfiguredFont(std.testing.allocator);
358 defer lookup.deinit(std.testing.allocator);
359
360 var face = try Face.init(std.testing.allocator, lookup.path, lookup.index, 14);
361 defer face.deinit();
362 const small_cell = face.cellWidth();
363
364 try face.reinit(lookup.path, lookup.index, 28);
365 const large_cell = face.cellWidth();
366
367 try std.testing.expect(large_cell > small_cell);
368 }