getTextureAtlas
This needs to be called before rendering of glyphs as the uv coordinates of the glyphs will not be correct if the atlas needs to be generated.
Parameters
Source
Implementation
pub fn getTextureAtlas(self: *Entry, gpa: std.mem.Allocator, backend: Backend) Backend.TextureError!Texture {
if (self.texture_atlas_cache) |tex| return tex;
// number of extra pixels to add on each side of each glyph
const pad = 1;
const total = self.glyph_info_ascii.len + self.glyph_info.count();
const row_glyphs: u32 = @ceil(@as(f32, @sqrt(@as(f32, @floatFromInt(total)))));
var s = Size{};
{
var i: u32 = 0;
var row: Size = .{};
var it = self.glyph_info.iterator();
while (i < total) {
const gi, const codepoint = if (i < self.glyph_info_ascii.len) .{ &self.glyph_info_ascii[i], i + ascii_start } else blk: {
const e = it.next().?;
break :blk .{ e.value_ptr, e.key_ptr.* };
};
_ = codepoint;
row.w += gi.w + 2 * pad;
row.h = @max(row.h, gi.h + 2 * pad);
i += 1;
if (i % row_glyphs == 0) {
s.w = @max(s.w, row.w);
s.h += row.h;
row = .{};
}
} else {
s.w = @max(s.w, row.w);
s.h += row.h;
}
s = s.ceil();
}
// also add an extra padding around whole texture
s.w += 2 * pad;
s.h += 2 * pad;
var pixels = try gpa.alloc(dvui.Color.PMA, @as(usize, @trunc(s.w * s.h)));
defer gpa.free(pixels);
// set all pixels to zero alpha
@memset(pixels, .transparent);
//const num_glyphs = fce.glyph_info.count();
//std.debug.print("font size {d} regen glyph atlas num {d} max size {}\n", .{ sized_font.size, num_glyphs, s });
var x: i32 = pad;
var y: i32 = pad;
var it = self.glyph_info.iterator();
var row_height: u32 = 0;
var i: u32 = 0;
while (i < total) {
var gi, const codepoint = if (i < self.glyph_info_ascii.len) .{ &self.glyph_info_ascii[i], i + ascii_start } else blk: {
const e = it.next().?;
break :blk .{ e.value_ptr, e.key_ptr.* };
};
gi.uv[0] = @as(f32, @floatFromInt(x + pad)) / s.w;
gi.uv[1] = @as(f32, @floatFromInt(y + pad)) / s.h;
if (impl == .FreeType) blk: {
FreeType.intToError(c.FT_Load_Char(self.face, codepoint, @as(i32, @bitCast(FreeType.LoadFlags{ .render = true })))) catch |err| {
dvui.log.warn("renderText: freetype error {any} trying to FT_Load_Char codepoint {d}", .{ err, codepoint });
break :blk; // will skip the failing glyph
};
// https://freetype.org/freetype2/docs/tutorial/step1.html#section-6
if (self.face.*.glyph.*.format != c.FT_GLYPH_FORMAT_BITMAP) {
FreeType.intToError(c.FT_Render_Glyph(self.face.*.glyph, c.FT_RENDER_MODE_NORMAL)) catch |err| {
dvui.log.warn("renderText freetype error {any} trying to FT_Render_Glyph codepoint {d}", .{ err, codepoint });
break :blk; // will skip the failing glyph
};
}
const bitmap = self.face.*.glyph.*.bitmap;
//std.debug.print("bitmap,{d},{d},{d}\n", .{codepoint, bitmap.width, bitmap.rows});
row_height = @max(row_height, bitmap.rows);
var row: i32 = 0;
while (row < bitmap.rows) : (row += 1) {
var col: i32 = 0;
while (col < bitmap.width) : (col += 1) {
const src = bitmap.buffer[@as(usize, @intCast(row * bitmap.pitch + col))];
// because of the extra edge, offset by 1 row and 1 col
const di = @as(usize, @intCast((y + row + pad) * @as(i32, @trunc(s.w)) + (x + col + pad)));
// premultiplied white
pixels[di] = .{ .r = src, .g = src, .b = src, .a = src };
}
}
} else {
//var ow: c_int = undefined;
//var oh: c_int = undefined;
//const bm = c.stbtt_GetCodepointBitmap(&self.face, self.scaleFactor, self.scaleFactor, @as(c_int, @intCast(codepoint)), &ow, &oh, null, null);
//std.debug.print("bitmap,{d},{d},{d}\n", .{codepoint, ow, oh});
//c.stbtt_FreeBitmap(bm, null);
const out_w: u32 = @trunc(gi.w);
const out_h: u32 = @trunc(gi.h);
row_height = @max(row_height, out_h);
// single channel
const bitmap = try gpa.alloc(u8, @as(usize, out_w * out_h));
defer gpa.free(bitmap);
//log.debug("makecodepointBitmap size x {d} y {d} w {d} h {d} out w {d} h {d}", .{ x, y, s.w, s.h, out_w, out_h });
c.stbtt_MakeCodepointBitmapSubpixel(&self.face, bitmap.ptr, @as(c_int, @intCast(out_w)), @as(c_int, @intCast(out_h)), @as(c_int, @intCast(out_w)), self.scaleFactor, self.scaleFactor, 0.0, 0.0, @as(c_int, @intCast(codepoint)));
const stride: usize = @trunc(s.w);
const di = @as(usize, @intCast(y)) * stride + @as(usize, @intCast(x));
for (0..out_h) |row| {
for (0..out_w) |col| {
const src = bitmap[row * out_w + col];
const dest = di + (row + pad) * stride + (col + pad);
// premultiplied white
pixels[dest] = .{ .r = src, .g = src, .b = src, .a = src };
}
}
}
x += @as(i32, @trunc(gi.w)) + 2 * pad;
i += 1;
if (i % row_glyphs == 0) {
x = pad;
y += @intCast(row_height + 2 * pad);
row_height = 0;
}
}
self.texture_atlas_cache = try backend.textureCreate(@ptrCast(pixels.ptr), .{ .width = @trunc(s.w), .height = @trunc(s.h) });
return self.texture_atlas_cache.?;
}